From a20b608d3479d708aa692244af04c2fa175b6e8f Mon Sep 17 00:00:00 2001 From: Chaitanya Deorukhkar Date: Wed, 27 Apr 2022 16:50:04 +0530 Subject: [PATCH] feat(blade): add motion tokens (#438) Co-authored-by: Kamlesh Chandnani --- .changeset/brave-jeans-boil.md | 13 ++ packages/blade/docs/components/MovingDiv.js | 21 ++ packages/blade/docs/tokens/Motion.stories.mdx | 199 ++++++++++++++++++ packages/blade/ios/Podfile.lock | 33 ++- packages/blade/jest-setup.native.js | 8 + packages/blade/package.json | 2 + .../__tests__/paymentLightTheme/index.ts | 1 + .../paymentLightTheme/paymentLightTheme.d.ts | 2 + .../paymentLightTheme.native.ts | 31 +++ .../paymentLightTheme.web.ts} | 49 ++++- .../components/BladeProvider/emptyTheme.ts | 37 ++++ .../src/components/BladeProvider/index.ts | 2 + packages/blade/src/tokens/global/index.ts | 1 + packages/blade/src/tokens/global/motion.ts | 140 ++++++++++++ .../blade/src/tokens/theme/bankingTheme.ts | 2 + .../blade/src/tokens/theme/paymentTheme.ts | 2 + packages/blade/src/tokens/theme/theme.d.ts | 2 + packages/blade/src/utils/makeBezier/index.ts | 1 + .../src/utils/makeBezier/makeBezier.d.ts | 2 + .../makeBezier/makeBezier.native.test.ts | 11 + .../src/utils/makeBezier/makeBezier.native.ts | 8 + .../utils/makeBezier/makeBezier.web.test.ts | 8 + .../src/utils/makeBezier/makeBezier.web.ts | 10 + .../blade/src/utils/makeMotionTime/index.ts | 1 + .../utils/makeMotionTime/makeMotionTime.d.ts | 1 + .../makeMotionTime.native.test.ts | 14 ++ .../makeMotionTime/makeMotionTime.native.ts | 5 + .../makeMotionTime/makeMotionTime.web.test.ts | 14 ++ .../makeMotionTime/makeMotionTime.web.ts | 5 + yarn.lock | 58 +++++ 30 files changed, 676 insertions(+), 7 deletions(-) create mode 100644 .changeset/brave-jeans-boil.md create mode 100644 packages/blade/docs/components/MovingDiv.js create mode 100644 packages/blade/docs/tokens/Motion.stories.mdx create mode 100644 packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/index.ts create mode 100644 packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.d.ts create mode 100644 packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.native.ts rename packages/blade/src/components/BladeProvider/__tests__/{paymentLightTheme.ts => paymentLightTheme/paymentLightTheme.web.ts} (96%) create mode 100644 packages/blade/src/tokens/global/motion.ts create mode 100644 packages/blade/src/utils/makeBezier/index.ts create mode 100644 packages/blade/src/utils/makeBezier/makeBezier.d.ts create mode 100644 packages/blade/src/utils/makeBezier/makeBezier.native.test.ts create mode 100644 packages/blade/src/utils/makeBezier/makeBezier.native.ts create mode 100644 packages/blade/src/utils/makeBezier/makeBezier.web.test.ts create mode 100644 packages/blade/src/utils/makeBezier/makeBezier.web.ts create mode 100644 packages/blade/src/utils/makeMotionTime/index.ts create mode 100644 packages/blade/src/utils/makeMotionTime/makeMotionTime.d.ts create mode 100644 packages/blade/src/utils/makeMotionTime/makeMotionTime.native.test.ts create mode 100644 packages/blade/src/utils/makeMotionTime/makeMotionTime.native.ts create mode 100644 packages/blade/src/utils/makeMotionTime/makeMotionTime.web.test.ts create mode 100644 packages/blade/src/utils/makeMotionTime/makeMotionTime.web.ts diff --git a/.changeset/brave-jeans-boil.md b/.changeset/brave-jeans-boil.md new file mode 100644 index 00000000000..bb5df92a03b --- /dev/null +++ b/.changeset/brave-jeans-boil.md @@ -0,0 +1,13 @@ +--- +"@razorpay/blade": minor +--- + +feat(blade): add motion tokens + +### Motion tokens +We have added tokens for +1. Delay +2. Duration +3. Easing + +You can find a detailed RFC for motion here: [View Formatted RFC](https://github.com/razorpay/blade/blob/rfc/2022-03-22-motion-rfc/rfcs/2022-03-22-motion-rfc.md) diff --git a/packages/blade/docs/components/MovingDiv.js b/packages/blade/docs/components/MovingDiv.js new file mode 100644 index 00000000000..5b4529d9e08 --- /dev/null +++ b/packages/blade/docs/components/MovingDiv.js @@ -0,0 +1,21 @@ +import styled from 'styled-components'; + +const MovingDiv = styled.div` + height: 40px; + width: 40px; + background-color: ${(props) => props.theme.colors.brand.primary[500]}; + animation: ${(props) => `move 3s ${props.easing || 'linear'} infinite`}; + @keyframes move { + 0% { + transform: translateX(0px); + } + 50% { + transform: translateX(100px); + } + 100% { + transform: translateX(0px); + } + } +`; + +export default MovingDiv; diff --git a/packages/blade/docs/tokens/Motion.stories.mdx b/packages/blade/docs/tokens/Motion.stories.mdx new file mode 100644 index 00000000000..32ff6a2c9b2 --- /dev/null +++ b/packages/blade/docs/tokens/Motion.stories.mdx @@ -0,0 +1,199 @@ +import { Meta, DocsContainer } from '@storybook/addon-docs'; +import styled from 'styled-components'; +import { useTheme, BladeProvider } from '../../src/components'; +import { paymentTheme, bankingTheme } from '../../src/tokens'; +import makeMotionTime from '../../src/utils/makeMotionTime'; +import MovingDiv from '../components/MovingDiv'; + + { + const getThemeTokens = () => { + if (context.globals.themeTokens === 'paymentTheme') { + return paymentTheme; + } + if (context.globals.themeTokens === 'bankingTheme') { + return bankingTheme; + } + return null; + }; + return ( + + + {children} + + + ); + }, + }, + }} +/> + +# 🎬 Motion + +export const Motion = () => { + const { theme } = useTheme(); + return ( + <> +

Delay (in milliseconds)

+ + + {Object.entries(theme.motion.delay).map(([key, value]) => ( + + + + + ))} + +
{`theme.motion.delay.${key}`}{value}
+

Duration (in milliseconds)

+ + + {Object.entries(theme.motion.duration).map(([key, value]) => ( + + + + + ))} + +
{`theme.motion.duration.${key}`}{value}
+

Easing

+

standard

+ + + {Object.entries(theme.motion.easing.standard).map(([key, value]) => ( + + + + + + ))} + +
{`theme.motion.easing.standard.${key}`}{value} + +
+

entrance

+ + + {Object.entries(theme.motion.easing.entrance).map(([key, value]) => ( + + + + + + ))} + +
{`theme.motion.easing.entrance.${key}`}{value} + +
+

exit

+ + + {Object.entries(theme.motion.easing.exit).map(([key, value]) => ( + + + + + + ))} + +
{`theme.motion.easing.exit.${key}`}{value} + +
+ + ); +}; + +export const MotionExample = () => { + const { theme } = useTheme(); + const ExampleDiv = styled.div` + height: 50px; + width: 50px; + border-radius: 25px; + background-color: ${(props) => props.theme.colors.brand.primary[500]}; + animation: ${(props) => + `resize ${makeMotionTime(props.theme.motion.duration.xgentle)} ${ + props.theme.motion.easing.standard.effective + } infinite`}; + @keyframes resize { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.5); + } + 100% { + transform: scale(1); + } + } + `; + return ; +}; + + + +## Example Usage + +If we want to create a circle that scales up and down with a **duration** of `xgentle` and an **easing** of `standard.effective` + +1. Ensure you've followed all the steps under ["Guides/Usage"](https://master--61c19ee8d3d282003ac1d81c.chromatic.com//?path=/story/guides-usage--page&globals=measureEnabled:false) to setup your theme with `` +2. Your theme tokens will be automatically available to `styled-components` as a `theme` prop +3. Create a component using `styled-components` that looks like this: + +```jsx +import styled from 'styled-components'; +import { makeMotionTime } from '@razorpay/blade/utils'; + +const ExampleDiv = styled.div` + height: 50px; + width: 50px; + border-radius: 25px; + background-color: ${(props) => props.theme.colors.brand.primary[500]}; + animation: ${(props) => + `resize ${makeMotionTime(props.theme.motion.duration.xgentle)} ${ + props.theme.motion.easing.standard.effective + } infinite`}; + @keyframes resize { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.5); + } + 100% { + transform: scale(1); + } + } +`; +``` + +4. You can also access your motion tokens using the `useTheme` hook + +```jsx +import { useTheme } from '@razorpay/blade/components'; +import { makeMotionTime } from '@razorpay/blade/utils'; + +const CustomComponent = () => { + const { theme } = useTheme(); + const easing = theme.motion.easing.standard.effective; + const duration = makeMotionTime(theme.motion.duration.xgentle); + const delay = makeMotionTime(theme.motion.delay.short); + + return ( + ... + ); +}; +``` + +> Note: `makeMotionTime` is a helper function that converts `duration` & `delay` to a platform specific unit for web & native. You should always use this helper function while consuming `duration` & `delay` tokens + +### Output: + +
+ diff --git a/packages/blade/ios/Podfile.lock b/packages/blade/ios/Podfile.lock index 4666a783f1a..76a7fd69d6d 100644 --- a/packages/blade/ios/Podfile.lock +++ b/packages/blade/ios/Podfile.lock @@ -280,6 +280,33 @@ PODS: - React-Core - RNDateTimePicker (5.0.1): - React-Core + - RNReanimated (2.6.0): + - DoubleConversion + - FBLazyVector + - FBReactNativeSpec + - glog + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-Core/DevSupport + - React-Core/RCTWebSocket + - React-CoreModules + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-RCTActionSheet + - React-RCTAnimation + - React-RCTBlob + - React-RCTImage + - React-RCTLinking + - React-RCTNetwork + - React-RCTSettings + - React-RCTText + - ReactCommon/turbomodule/core + - Yoga - Yoga (1.14.0) DEPENDENCIES: @@ -317,6 +344,7 @@ DEPENDENCIES: - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)" + - RNReanimated (from `../node_modules/react-native-reanimated`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -388,6 +416,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-async-storage/async-storage" RNDateTimePicker: :path: "../node_modules/@react-native-community/datetimepicker" + RNReanimated: + :path: "../node_modules/react-native-reanimated" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -425,8 +455,9 @@ SPEC CHECKSUMS: ReactCommon: 57b69f6383eafcbd7da625bfa6003810332313c4 RNCAsyncStorage: ea6b5c280997b2b32a587793163b1f10e580c4f7 RNDateTimePicker: d16386fda012db9e1fac5d2f8281a91254dfee99 + RNReanimated: 8135778ca3fa9dd6cfdd74d0218106131394e2ec Yoga: e7dc4e71caba6472ff48ad7d234389b91dadc280 -PODFILE CHECKSUM: 631d33190c61865906079886cc8cec997d4bde37 +PODFILE CHECKSUM: 572e41323005ea0d32663d81a2a36d83084502f2 COCOAPODS: 1.11.2 diff --git a/packages/blade/jest-setup.native.js b/packages/blade/jest-setup.native.js index 79cce2519dd..d4a4a94b66f 100644 --- a/packages/blade/jest-setup.native.js +++ b/packages/blade/jest-setup.native.js @@ -4,3 +4,11 @@ Object.defineProperty(global.navigator, 'product', { value: 'ReactNative', }); + +jest.mock('react-native-reanimated', () => ({ + Easing: { + bezier: jest.fn((x1, y1, x2, y2) => { + return `${x1} ${y1} ${x2} ${y2}`; // mock an implementation of Easing.bezier that returns a string + }), + }, +})); diff --git a/packages/blade/package.json b/packages/blade/package.json index 38ea6554ab2..b63d3ae5343 100644 --- a/packages/blade/package.json +++ b/packages/blade/package.json @@ -113,6 +113,7 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-native": "0.66.4", + "react-native-reanimated": "2.6.0", "react-scripts": "4.0.3", "react-test-renderer": "17.0.2", "rollup": "2.61.1", @@ -124,6 +125,7 @@ "@babel/runtime": "^7.16.5", "react-dom": "^17.0.2", "react-native": "^0.66.4", + "react-native-reanimated": "^2.6.0", "react": "^17.0.2", "styled-components": "^5" }, diff --git a/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/index.ts b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/index.ts new file mode 100644 index 00000000000..ee558e7817e --- /dev/null +++ b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/index.ts @@ -0,0 +1 @@ +export { default } from './paymentLightTheme'; diff --git a/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.d.ts b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.d.ts new file mode 100644 index 00000000000..f20774786e2 --- /dev/null +++ b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.d.ts @@ -0,0 +1,2 @@ +// @TODO: figure out if we can just declare types from here and then export it. So this way we don't have to do this dirty `.web` thing +export { default } from './paymentLightTheme.web'; diff --git a/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.native.ts b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.native.ts new file mode 100644 index 00000000000..454d0c01107 --- /dev/null +++ b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.native.ts @@ -0,0 +1,31 @@ +import { Easing } from 'react-native-reanimated'; + +import type { Theme } from '../../'; +import paymentLightThemeWeb from './paymentLightTheme.web'; + +const paymentLightTheme: Theme = { + ...paymentLightThemeWeb, + motion: { + ...paymentLightThemeWeb.motion, + easing: { + standard: { + attentive: Easing.bezier(0.5, 0, 0.3, 1.5), + effective: Easing.bezier(0.3, 0, 0.2, 1), + revealing: Easing.bezier(0.5, 0, 0, 1), + wary: Easing.bezier(1, 0.5, 0, 0.5), + }, + entrance: { + attentive: Easing.bezier(0.5, 0, 0.3, 1.5), + effective: Easing.bezier(0, 0, 0.2, 1), + revealing: Easing.bezier(0, 0, 0, 1), + }, + exit: { + attentive: Easing.bezier(0.7, 0, 0.5, 1), + effective: Easing.bezier(0.17, 0, 1, 1), + revealing: Easing.bezier(0.5, 0, 1, 1), + }, + }, + }, +}; + +export default paymentLightTheme; diff --git a/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme.ts b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.web.ts similarity index 96% rename from packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme.ts rename to packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.web.ts index 87a20d57eb1..9d7546b6745 100644 --- a/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme.ts +++ b/packages/blade/src/components/BladeProvider/__tests__/paymentLightTheme/paymentLightTheme.web.ts @@ -1,9 +1,9 @@ -import globalColors from '../../../tokens/global/colors'; -import border from '../../../tokens/global/border'; -import breakpoints from '../../../tokens/global/breakpoints'; -import spacing from '../../../tokens/global/spacing'; -import typography from '../../../tokens/global/typography'; -import type { Theme } from '../'; +import globalColors from '../../../../tokens/global/colors'; +import border from '../../../../tokens/global/border'; +import breakpoints from '../../../../tokens/global/breakpoints'; +import spacing from '../../../../tokens/global/spacing'; +import typography from '../../../../tokens/global/typography'; +import type { Theme } from '../../'; const paymentLightTheme: Theme = { colors: { @@ -1021,6 +1021,43 @@ const paymentLightTheme: Theme = { }, }, border, + motion: { + delay: { + '2xshort': 70, + xshort: 120, + short: 180, + long: 3000, + xlong: 5000, + }, + duration: { + '2xquick': 70, + xquick: 150, + quick: 200, + moderate: 250, + xmoderate: 300, + gentle: 400, + xgentle: 600, + '2xgentle': 900, + }, + easing: { + standard: { + attentive: 'cubic-bezier(0.5, 0, 0.3, 1.5)', + effective: 'cubic-bezier(0.3, 0, 0.2, 1)', + revealing: 'cubic-bezier(0.5, 0, 0, 1)', + wary: 'cubic-bezier(1, 0.5, 0, 0.5)', + }, + entrance: { + attentive: 'cubic-bezier(0.5, 0, 0.3, 1.5)', + effective: 'cubic-bezier(0, 0, 0.2, 1)', + revealing: 'cubic-bezier(0, 0, 0, 1)', + }, + exit: { + attentive: 'cubic-bezier(0.7, 0, 0.5, 1)', + effective: 'cubic-bezier(0.17, 0, 1, 1)', + revealing: 'cubic-bezier(0.5, 0, 1, 1)', + }, + }, + }, spacing, breakpoints, shadows: { diff --git a/packages/blade/src/components/BladeProvider/emptyTheme.ts b/packages/blade/src/components/BladeProvider/emptyTheme.ts index 34ed9fbcc9b..3c9ce6d7527 100644 --- a/packages/blade/src/components/BladeProvider/emptyTheme.ts +++ b/packages/blade/src/components/BladeProvider/emptyTheme.ts @@ -1037,6 +1037,43 @@ const emptyTheme: Theme = { xl: 0, max: 0, }, + motion: { + delay: { + '2xshort': 70, + xshort: 120, + short: 180, + long: 3000, + xlong: 5000, + }, + duration: { + '2xquick': 70, + xquick: 150, + quick: 200, + moderate: 250, + xmoderate: 300, + gentle: 400, + xgentle: 600, + '2xgentle': 900, + }, + easing: { + standard: { + attentive: 'cubic-bezier(0.5, 0, 0.3, 1.5)', + effective: 'cubic-bezier(0.3, 0, 0.2, 1)', + revealing: 'cubic-bezier(0.5, 0, 0, 1)', + wary: 'cubic-bezier(1, 0.5, 0, 0.5)', + }, + entrance: { + attentive: 'cubic-bezier(0.5, 0, 0.3, 1.5)', + effective: 'cubic-bezier(0, 0, 0.2, 1)', + revealing: 'cubic-bezier(0, 0, 0, 1)', + }, + exit: { + attentive: 'cubic-bezier(0.7, 0, 0.5, 1)', + effective: 'cubic-bezier(0.17, 0, 1, 1)', + revealing: 'cubic-bezier(0.5, 0, 1, 1)', + }, + }, + }, spacing: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 }, shadows: { offsetX: { diff --git a/packages/blade/src/components/BladeProvider/index.ts b/packages/blade/src/components/BladeProvider/index.ts index 7c888dba2db..4254b6d6f0f 100644 --- a/packages/blade/src/components/BladeProvider/index.ts +++ b/packages/blade/src/components/BladeProvider/index.ts @@ -1,6 +1,7 @@ import type { Colors, Shadows, ShadowLevels } from '../../tokens/theme/theme.d'; import type { Border } from '../../tokens/global/border'; import type { Breakpoints } from '../../tokens/global/breakpoints'; +import type { Motion } from '../../tokens/global/motion'; import type { Spacing } from '../../tokens/global/spacing'; import type { Typography } from '../../tokens/global/typography'; export { default as BladeProvider } from './BladeProvider'; @@ -12,6 +13,7 @@ export type Theme = { breakpoints: Breakpoints; colors: Colors; spacing: Spacing; + motion: Motion; shadows: Omit & { color: { level: Record; diff --git a/packages/blade/src/tokens/global/index.ts b/packages/blade/src/tokens/global/index.ts index 86656639554..b23250312fa 100644 --- a/packages/blade/src/tokens/global/index.ts +++ b/packages/blade/src/tokens/global/index.ts @@ -6,3 +6,4 @@ export type { FontFamily } from './fontFamily.d'; export type { Opacity } from './opacity'; export type { Typography } from './typography'; export type { Spacing } from './spacing'; +export type { Motion } from './motion'; diff --git a/packages/blade/src/tokens/global/motion.ts b/packages/blade/src/tokens/global/motion.ts new file mode 100644 index 00000000000..9da429d3ad1 --- /dev/null +++ b/packages/blade/src/tokens/global/motion.ts @@ -0,0 +1,140 @@ +import makeBezier from '../../utils/makeBezier'; + +type Duration = { + /** `70` milliseconds */ + '2xquick': 70; + /** `150` milliseconds */ + xquick: 150; + /** `200` milliseconds */ + quick: 200; + /** `250` milliseconds */ + moderate: 250; + /** `300` milliseconds */ + xmoderate: 300; + /** `400` milliseconds */ + gentle: 400; + /** `600` milliseconds */ + xgentle: 600; + /** `900` milliseconds */ + '2xgentle': 900; +}; + +type Delay = { + /** `70` milliseconds */ + '2xshort': 70; + /** `120` milliseconds */ + xshort: 120; + /** `180` milliseconds */ + short: 180; + /** `3000` milliseconds */ + long: 3000; + /** `5000` milliseconds */ + xlong: 5000; +}; + +type EasingFunctionFactory = { factory: () => (value: number) => number }; // similar to EasingFunctionFactory of `react-native-reanimated` + +type Easing = { + /** Easings for all standard animations*/ + standard: { + /** `cubic-bezier(0.5, 0, 0.3, 1.5)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + attentive: EasingFunctionFactory | 'cubic-bezier(0.5, 0, 0.3, 1.5)'; + /** `cubic-bezier(0.3, 0, 0.2, 1)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + effective: EasingFunctionFactory | 'cubic-bezier(0.3, 0, 0.2, 1)'; + /** `cubic-bezier(0.5, 0, 0, 1)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + revealing: EasingFunctionFactory | 'cubic-bezier(0.5, 0, 0, 1)'; + /** `cubic-bezier(1, 0.5, 0, 0.5)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + wary: EasingFunctionFactory | 'cubic-bezier(1, 0.5, 0, 0.5)'; + }; + /** Easings for all entrance animations*/ + entrance: { + /** `cubic-bezier(0.5, 0, 0.3, 1.5)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + attentive: EasingFunctionFactory | 'cubic-bezier(0.5, 0, 0.3, 1.5)'; + /** `cubic-bezier(0, 0, 0.2, 1)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + effective: EasingFunctionFactory | 'cubic-bezier(0, 0, 0.2, 1)'; + /** `cubic-bezier(0, 0, 0, 1)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + revealing: EasingFunctionFactory | 'cubic-bezier(0, 0, 0, 1)'; + }; + /** Easings for all exit animations*/ + exit: { + /** `cubic-bezier(0.7, 0, 0.5, 1)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + attentive: EasingFunctionFactory | 'cubic-bezier(0.7, 0, 0.5, 1)'; + /** `cubic-bezier(0.17, 0, 1, 1)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + effective: EasingFunctionFactory | 'cubic-bezier(0.17, 0, 1, 1)'; + /** `cubic-bezier(0.5, 0, 1, 1)` + * + * Returns a `string` of `"cubic-bezier(...)"` for web & react-native-reanimated's Easing Function of type `EasingFunctionFactory` for native */ + revealing: EasingFunctionFactory | 'cubic-bezier(0.5, 0, 1, 1)'; + }; +}; + +export type Motion = Readonly<{ + delay: Delay; + duration: Duration; + easing: Easing; +}>; + +const delay: Delay = { + '2xshort': 70, + xshort: 120, + short: 180, + long: 3000, + xlong: 5000, +}; + +const duration: Duration = { + '2xquick': 70, + xquick: 150, + quick: 200, + moderate: 250, + xmoderate: 300, + gentle: 400, + xgentle: 600, + '2xgentle': 900, +}; + +/* makeBezier returns a string of `cubic-bezier()` for web & a react-native-reanimated Easing Function of type `EasingFunctionFactory` for native */ +const easing: Easing = { + standard: { + attentive: makeBezier(0.5, 0, 0.3, 1.5), + effective: makeBezier(0.3, 0, 0.2, 1), + revealing: makeBezier(0.5, 0, 0, 1), + wary: makeBezier(1, 0.5, 0, 0.5), + }, + entrance: { + attentive: makeBezier(0.5, 0, 0.3, 1.5), + effective: makeBezier(0, 0, 0.2, 1), + revealing: makeBezier(0, 0, 0, 1), + }, + exit: { + attentive: makeBezier(0.7, 0, 0.5, 1), + effective: makeBezier(0.17, 0, 1, 1), + revealing: makeBezier(0.5, 0, 1, 1), + }, +}; + +const motion: Motion = { + delay, + duration, + easing, +}; + +export default motion; diff --git a/packages/blade/src/tokens/theme/bankingTheme.ts b/packages/blade/src/tokens/theme/bankingTheme.ts index aad0d13c93f..16ff8afc0ca 100644 --- a/packages/blade/src/tokens/theme/bankingTheme.ts +++ b/packages/blade/src/tokens/theme/bankingTheme.ts @@ -3,6 +3,7 @@ import breakpoints from '../global/breakpoints'; import spacing from '../global/spacing'; import globalColors from '../global/colors'; import typography from '../global/typography'; +import motion from '../global/motion'; import type { ThemeTokens, ColorsWithModes, Shadows } from './theme.d'; const colors: ColorsWithModes = { @@ -2101,6 +2102,7 @@ const bankingTheme: ThemeTokens = { border, breakpoints, colors, + motion, spacing, shadows, typography, diff --git a/packages/blade/src/tokens/theme/paymentTheme.ts b/packages/blade/src/tokens/theme/paymentTheme.ts index 637dccf673a..64e0bd3fbd7 100644 --- a/packages/blade/src/tokens/theme/paymentTheme.ts +++ b/packages/blade/src/tokens/theme/paymentTheme.ts @@ -3,6 +3,7 @@ import breakpoints from '../global/breakpoints'; import spacing from '../global/spacing'; import globalColors from '../global/colors'; import typography from '../global/typography'; +import motion from '../global/motion'; import type { ThemeTokens, ColorsWithModes, Shadows } from './theme.d'; const colors: ColorsWithModes = { @@ -2101,6 +2102,7 @@ const paymentTheme: ThemeTokens = { border, breakpoints, colors, + motion, spacing, shadows, typography, diff --git a/packages/blade/src/tokens/theme/theme.d.ts b/packages/blade/src/tokens/theme/theme.d.ts index 23189e56bf6..8c2ba247c9a 100644 --- a/packages/blade/src/tokens/theme/theme.d.ts +++ b/packages/blade/src/tokens/theme/theme.d.ts @@ -1,5 +1,6 @@ import type { Border } from '../global/border'; import type { Breakpoints } from '../global/breakpoints'; +import type { Motion } from '../global/motion'; import type { Spacing } from '../global/spacing'; import type { TypographyWithPlatforms } from '../global/typography'; @@ -117,6 +118,7 @@ export type ThemeTokens = { border: Border; breakpoints: Breakpoints; colors: ColorsWithModes; + motion: Motion; spacing: Spacing; shadows: Shadows; typography: TypographyWithPlatforms; diff --git a/packages/blade/src/utils/makeBezier/index.ts b/packages/blade/src/utils/makeBezier/index.ts new file mode 100644 index 00000000000..ae3a12f0586 --- /dev/null +++ b/packages/blade/src/utils/makeBezier/index.ts @@ -0,0 +1 @@ +export { default } from './makeBezier'; diff --git a/packages/blade/src/utils/makeBezier/makeBezier.d.ts b/packages/blade/src/utils/makeBezier/makeBezier.d.ts new file mode 100644 index 00000000000..a32180ffd65 --- /dev/null +++ b/packages/blade/src/utils/makeBezier/makeBezier.d.ts @@ -0,0 +1,2 @@ +// @TODO: figure out if we can just declare types from here and then export it. So this way we don't have to do this dirty `.web` thing +export { default } from './makeBezier.web'; diff --git a/packages/blade/src/utils/makeBezier/makeBezier.native.test.ts b/packages/blade/src/utils/makeBezier/makeBezier.native.test.ts new file mode 100644 index 00000000000..e148a15451a --- /dev/null +++ b/packages/blade/src/utils/makeBezier/makeBezier.native.test.ts @@ -0,0 +1,11 @@ +import { Easing } from 'react-native-reanimated'; +import makeBezier from './'; + +describe('makeBezier', () => { + it.only(`should call the Easing Function from react-native-reanimated with the right parameters`, () => { + const easing = makeBezier(0.5, 1, 0, 1.5); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(Easing.bezier).toHaveBeenCalledWith(0.5, 1, 0, 1.5); // ensure Easing.bezier is called + expect(easing).toBe('0.5 1 0 1.5'); // ensure that returned value of Easing.bezier is also returned by makeBezier + }); +}); diff --git a/packages/blade/src/utils/makeBezier/makeBezier.native.ts b/packages/blade/src/utils/makeBezier/makeBezier.native.ts new file mode 100644 index 00000000000..4a21d2b8d65 --- /dev/null +++ b/packages/blade/src/utils/makeBezier/makeBezier.native.ts @@ -0,0 +1,8 @@ +import { Easing } from 'react-native-reanimated'; +import type { EasingFunctionFactory } from 'react-native-reanimated'; + +const makeBezier = (x1: number, y1: number, x2: number, y2: number): EasingFunctionFactory => { + return Easing.bezier(x1, y1, x2, y2); +}; + +export default makeBezier; diff --git a/packages/blade/src/utils/makeBezier/makeBezier.web.test.ts b/packages/blade/src/utils/makeBezier/makeBezier.web.test.ts new file mode 100644 index 00000000000..538fe6b52f6 --- /dev/null +++ b/packages/blade/src/utils/makeBezier/makeBezier.web.test.ts @@ -0,0 +1,8 @@ +import makeBezier from './'; + +describe('makeBezier', () => { + it(`should return a string of cubic-bezier with the right parameters`, () => { + const easing = makeBezier(0.5, 1, 0, 1.5); + expect(easing).toBe('cubic-bezier(0.5, 1, 0, 1.5)'); + }); +}); diff --git a/packages/blade/src/utils/makeBezier/makeBezier.web.ts b/packages/blade/src/utils/makeBezier/makeBezier.web.ts new file mode 100644 index 00000000000..c30fe8b1d67 --- /dev/null +++ b/packages/blade/src/utils/makeBezier/makeBezier.web.ts @@ -0,0 +1,10 @@ +const makeBezier = ( + x1: X1, + y1: Y1, + x2: X2, + y2: Y2, +): `cubic-bezier(${X1}, ${Y1}, ${X2}, ${Y2})` => { + return `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`; +}; + +export default makeBezier; diff --git a/packages/blade/src/utils/makeMotionTime/index.ts b/packages/blade/src/utils/makeMotionTime/index.ts new file mode 100644 index 00000000000..b312d8b9a9a --- /dev/null +++ b/packages/blade/src/utils/makeMotionTime/index.ts @@ -0,0 +1 @@ +export { default } from './makeMotionTime'; diff --git a/packages/blade/src/utils/makeMotionTime/makeMotionTime.d.ts b/packages/blade/src/utils/makeMotionTime/makeMotionTime.d.ts new file mode 100644 index 00000000000..eb27af19543 --- /dev/null +++ b/packages/blade/src/utils/makeMotionTime/makeMotionTime.d.ts @@ -0,0 +1 @@ +export { default } from './makeMotionTime.web'; diff --git a/packages/blade/src/utils/makeMotionTime/makeMotionTime.native.test.ts b/packages/blade/src/utils/makeMotionTime/makeMotionTime.native.test.ts new file mode 100644 index 00000000000..49ef15c3141 --- /dev/null +++ b/packages/blade/src/utils/makeMotionTime/makeMotionTime.native.test.ts @@ -0,0 +1,14 @@ +import motion from '../../tokens/global/motion'; +import makeMotionTime from '.'; + +describe('makeMotionTime', () => { + it('should return the duration value in `number`', () => { + const duration = makeMotionTime(motion.duration.moderate); + expect(duration).toEqual(250); + }); + + it('should return the delay value in `number`', () => { + const delay = makeMotionTime(motion.delay.short); + expect(delay).toEqual(180); + }); +}); diff --git a/packages/blade/src/utils/makeMotionTime/makeMotionTime.native.ts b/packages/blade/src/utils/makeMotionTime/makeMotionTime.native.ts new file mode 100644 index 00000000000..e1aba4712ab --- /dev/null +++ b/packages/blade/src/utils/makeMotionTime/makeMotionTime.native.ts @@ -0,0 +1,5 @@ +const makeMotionTime = (time: T): T => { + return time; +}; + +export default makeMotionTime; diff --git a/packages/blade/src/utils/makeMotionTime/makeMotionTime.web.test.ts b/packages/blade/src/utils/makeMotionTime/makeMotionTime.web.test.ts new file mode 100644 index 00000000000..6f7eb83c014 --- /dev/null +++ b/packages/blade/src/utils/makeMotionTime/makeMotionTime.web.test.ts @@ -0,0 +1,14 @@ +import motion from '../../tokens/global/motion'; +import makeMotionTime from '.'; + +describe('makeMotionTime', () => { + it('should return the duration value in `ms`', () => { + const duration = makeMotionTime(motion.duration.moderate); + expect(duration).toEqual('250ms'); + }); + + it('should return the delay value in `ms`', () => { + const delay = makeMotionTime(motion.delay.short); + expect(delay).toEqual('180ms'); + }); +}); diff --git a/packages/blade/src/utils/makeMotionTime/makeMotionTime.web.ts b/packages/blade/src/utils/makeMotionTime/makeMotionTime.web.ts new file mode 100644 index 00000000000..e16cb1598ce --- /dev/null +++ b/packages/blade/src/utils/makeMotionTime/makeMotionTime.web.ts @@ -0,0 +1,5 @@ +const makeMotionTime = (time: T): `${T}ms` => { + return `${time}ms`; +}; + +export default makeMotionTime; diff --git a/yarn.lock b/yarn.lock index e811c65a1ee..26d35195c6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -303,6 +303,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074" integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ== +"@babel/helper-plugin-utils@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== + "@babel/helper-remap-async-to-generator@^7.16.5": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz#e706646dc4018942acb4b29f7e185bc246d65ac3" @@ -903,6 +908,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.5" +"@babel/plugin-transform-object-assign@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.16.7.tgz#5fe08d63dccfeb6a33aa2638faf98e5c584100f8" + integrity sha512-R8mawvm3x0COTJtveuoqZIjNypn2FjfvXZr4pSQ8VhEFBuQGBz4XhHasZtHXjgXU4XptZ4HtGof3NoYc93ZH9Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.16.5", "@babel/plugin-transform-object-super@^7.8.3": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz#8ccd9a1bcd3e7732ff8aa1702d067d8cd70ce380" @@ -5849,6 +5861,11 @@ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" integrity sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w== +"@types/invariant@^2.2.35": + version "2.2.35" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" + integrity sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg== + "@types/is-function@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.1.tgz#2d024eace950c836d9e3335a66b97960ae41d022" @@ -16259,6 +16276,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" @@ -20641,6 +20663,11 @@ react-focus-lock@^2.1.0: use-callback-ref "^1.2.5" use-sidecar "^1.0.5" +react-freeze@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.0.tgz#b21c65fe1783743007c8c9a2952b1c8879a77354" + integrity sha512-yQaiOqDmoKqks56LN9MTgY06O0qQHgV4FUrikH357DydArSZHQhl0BJFqGKIZoTqi8JizF9Dxhuk1FIZD6qCaw== + react-helmet-async@^1.0.2, react-helmet-async@^1.0.7: version "1.2.2" resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.2.2.tgz#38d58d32ebffbc01ba42b5ad9142f85722492389" @@ -20800,6 +20827,27 @@ react-native-reanimated@1.9.0: dependencies: fbjs "^1.0.0" +react-native-reanimated@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.6.0.tgz#500497339bec2d18fd4979dc07c831997c9aee82" + integrity sha512-TG7u0d1iTx6BRQXhUp9DKEW/9K6169qiX9vweC+qOcVffGSZvjDZ+OyyI0faXIDvcf5LRHAud3mNtO3ANaHRhQ== + dependencies: + "@babel/plugin-transform-object-assign" "^7.16.7" + "@types/invariant" "^2.2.35" + invariant "^2.2.4" + lodash.isequal "^4.5.0" + react-native-screens "^3.11.1" + setimmediate "^1.0.5" + string-hash-64 "^1.0.3" + +react-native-screens@^3.11.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.13.1.tgz#b3b1c5788dca25a71668909f66d87fb35c5c5241" + integrity sha512-xcrnuUs0qUrGpc2gOTDY4VgHHADQwp80mwR1prU/Q0JqbZN5W3koLhuOsT6FkSRKjR5t40l+4LcjhHdpqRB2HA== + dependencies: + react-freeze "^1.0.0" + warn-once "^0.1.0" + react-native-simple-markdown@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-native-simple-markdown/-/react-native-simple-markdown-1.1.0.tgz#4d462f8ced26393c5230467420c61a50cc6a8095" @@ -23021,6 +23069,11 @@ string-argv@0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== +string-hash-64@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322" + integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw== + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -24759,6 +24812,11 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.12" +warn-once@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.0.tgz#4f58d89b84f968d0389176aa99e0cf0f14ffd4c8" + integrity sha512-recZTSvuaH/On5ZU5ywq66y99lImWqzP93+AiUo9LUwG8gXHW+LJjhOd6REJHm7qb0niYqrEQJvbHSQfuJtTqA== + warning@^4.0.2, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"