Skip to content

Commit

Permalink
feat(blade): add motion tokens (#438)
Browse files Browse the repository at this point in the history
Co-authored-by: Kamlesh Chandnani <[email protected]>
  • Loading branch information
chaitanyadeorukhkar and kamleshchandnani authored Apr 27, 2022
1 parent 7072204 commit a20b608
Show file tree
Hide file tree
Showing 30 changed files with 676 additions and 7 deletions.
13 changes: 13 additions & 0 deletions .changeset/brave-jeans-boil.md
Original file line number Diff line number Diff line change
@@ -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)
21 changes: 21 additions & 0 deletions packages/blade/docs/components/MovingDiv.js
Original file line number Diff line number Diff line change
@@ -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;
199 changes: 199 additions & 0 deletions packages/blade/docs/tokens/Motion.stories.mdx
Original file line number Diff line number Diff line change
@@ -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';

<Meta
title="Tokens/Motion"
parameters={{
docs: {
container: ({ children, context }) => {
const getThemeTokens = () => {
if (context.globals.themeTokens === 'paymentTheme') {
return paymentTheme;
}
if (context.globals.themeTokens === 'bankingTheme') {
return bankingTheme;
}
return null;
};
return (
<DocsContainer context={context}>
<BladeProvider
key={`${context.globals.themeTokens}-${context.globals.colorScheme}`}
themeTokens={getThemeTokens()}
colorScheme={context.globals.colorScheme}
>
{children}
</BladeProvider>
</DocsContainer>
);
},
},
}}
/>

# 🎬 Motion

export const Motion = () => {
const { theme } = useTheme();
return (
<>
<h3>Delay (in milliseconds)</h3>
<table>
<tbody>
{Object.entries(theme.motion.delay).map(([key, value]) => (
<tr key={key}>
<td>{`theme.motion.delay.${key}`}</td>
<td>{value}</td>
</tr>
))}
</tbody>
</table>
<h3>Duration (in milliseconds)</h3>
<table>
<tbody>
{Object.entries(theme.motion.duration).map(([key, value]) => (
<tr key={key}>
<td>{`theme.motion.duration.${key}`}</td>
<td>{value}</td>
</tr>
))}
</tbody>
</table>
<h3>Easing</h3>
<h4>standard</h4>
<table>
<tbody>
{Object.entries(theme.motion.easing.standard).map(([key, value]) => (
<tr key={key}>
<td>{`theme.motion.easing.standard.${key}`}</td>
<td>{value}</td>
<td style={{ width: '150px' }}>
<MovingDiv theme={theme} easing={theme.motion.easing.standard[key]}></MovingDiv>
</td>
</tr>
))}
</tbody>
</table>
<h4>entrance</h4>
<table>
<tbody>
{Object.entries(theme.motion.easing.entrance).map(([key, value]) => (
<tr key={key}>
<td>{`theme.motion.easing.entrance.${key}`}</td>
<td>{value}</td>
<td style={{ width: '150px' }}>
<MovingDiv theme={theme} easing={theme.motion.easing.entrance[key]}></MovingDiv>
</td>
</tr>
))}
</tbody>
</table>
<h4>exit</h4>
<table>
<tbody>
{Object.entries(theme.motion.easing.exit).map(([key, value]) => (
<tr key={key}>
<td>{`theme.motion.easing.exit.${key}`}</td>
<td>{value}</td>
<td style={{ width: '150px' }}>
<MovingDiv theme={theme} easing={theme.motion.easing.exit[key]}></MovingDiv>
</td>
</tr>
))}
</tbody>
</table>
</>
);
};

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 <ExampleDiv theme={theme} easing={theme.motion.easing.standard.effective}></ExampleDiv>;
};

<Motion />

## 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 `<BladeProvider>`
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:

<br />
<MotionExample />
33 changes: 32 additions & 1 deletion packages/blade/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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
8 changes: 8 additions & 0 deletions packages/blade/jest-setup.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}),
},
}));
2 changes: 2 additions & 0 deletions packages/blade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './paymentLightTheme';
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit a20b608

Please sign in to comment.