diff --git a/packages/blade/.storybook/react/preview.tsx b/packages/blade/.storybook/react/preview.tsx index 1b0f6836183..cedf06abcd7 100644 --- a/packages/blade/.storybook/react/preview.tsx +++ b/packages/blade/.storybook/react/preview.tsx @@ -74,6 +74,17 @@ export const parameters = { ], 'Components', ['*', 'Interaction Tests', 'KitchenSink'], + 'Motion', + [ + 'Introduction to Motion', + 'Fade', + 'Move', + 'Slide', + '*', + 'AnimateInteractions', + 'Stagger', + 'Recipes', + ], 'Recipes', ], }, @@ -96,13 +107,15 @@ export const parameters = { } return ( - - {children} - + + + {children} + + ); }, @@ -148,7 +161,8 @@ const StoryCanvas = styled.div<{ context }>( context.kind.includes('/Carousel') || context.kind.includes('/TopNav') || context.kind.includes('/Examples') || - context.kind.includes('/SideNav') + context.kind.includes('/SideNav') || + context.kind.includes('/Recipes') ? '0rem' : '2rem' }; @@ -232,7 +246,6 @@ export const globalTypes = { showName: true, }, }, - // TODO: Rebranding - Uncomment this when we fix white-labeling brandColor: { name: 'Brand Color', description: 'Brand Color (You can pass any valid color to BladeProvider)', diff --git a/packages/blade/docs/guides/Installation.stories.mdx b/packages/blade/docs/guides/Installation.stories.mdx index 802be539168..308b9b4ef38 100644 --- a/packages/blade/docs/guides/Installation.stories.mdx +++ b/packages/blade/docs/guides/Installation.stories.mdx @@ -22,6 +22,8 @@ import { Box, } from '../../src/components'; +import MotionInstallation from '../../src/components/BaseMotion/docs/MotionInstallation.mdx'; + # Installation @@ -77,7 +79,8 @@ Before you install the package, make sure that you have performed the following 1. Basic Setup 2. Install Fonts - 3. TypeScript + 3. Setup Motion React + 4. TypeScript @@ -90,7 +93,7 @@ Before you install the package, make sure that you have performed the following Blade has a few peer dependencies that you may already have installed in your project. If so, you can skip adding them again. ```shell - yarn add @razorpay/blade styled-components@5.3.11 @razorpay/i18nify-js@1.9.3 @razorpay/i18nify-react@4.0.8 + yarn add @razorpay/blade styled-components@5.3.11 @razorpay/i18nify-js@1.9.3 @razorpay/i18nify-react@4.0.8 motion@11.12.0 ``` @@ -248,6 +251,16 @@ Before you install the package, make sure that you have performed the following ``` + + + Add motion to your application + + Assuming you've followed the first step and installed `motion` in your project, Here's how we recommend you to setup the project- + + + + + TypeScript Setup diff --git a/packages/blade/docs/migration-docs/upgrade-v11.md b/packages/blade/docs/migration-docs/upgrade-v11.md new file mode 100644 index 00000000000..acc79a89770 --- /dev/null +++ b/packages/blade/docs/migration-docs/upgrade-v11.md @@ -0,0 +1,536 @@ +# Upgrade Guide for v11 (Brand Refresh) + +## Upgrade Workflow Overview + +All the rebranding upgrade activity starts at the design end and is then followed by engineering + +Upgrade Workflow Overview + +## Migration with Codemod + +**Step 1:** Install this version of Blade as `yarn add @razorpay/blade-rebranded@npm:@razorpay/blade@v11.0.0`. + +**Step 2:** Install new fonts (Inter & Tasa) by following [this file](https://blade.razorpay.com/?path=/docs/guides-installation--docs#-installing-fonts). + +**Step 3:** The codemod will update the components to the new version of Blade. Execute the codemod on the file/directory that needs to be migrated for the page via the following command: + +> Need help? Check out [jscodeshift docs](https://github.com/facebook/jscodeshift) for CLI usage tips. + +```sh +npx jscodeshift ./PATH_TO_YOUR_DIR --extensions=tsx,ts,jsx,js -t ./node_modules/@razorpay/blade-rebranded/codemods/brand-refresh/transformers/index.ts --ignore-pattern="**/node_modules/**" +``` + +### 🚧 Watch Out for Limitations & Edge Cases + +> [!IMPORTANT] +> +> There might be some situations where the codemod falls short. If you encounter errors, handle those cases manually by following up with your designer. + +- The codemod doesn't handle the migration of conditionally rendered props. Take a moment to manually inspect and update such cases. The codemod will also log a warning for such cases with the line number & path to the file. For example: `Expression found in the "size" attribute, please update manually: src/pages/ResumeWithRazorpay/sections/WhyResumeSection.tsx:20` + + ```diff + - Hello + + Hello + ``` + +- With Blade v11, we have removed `highContrast` & `lowContrast` terminology from color tokens. If you have used any color token which has `highContrast` in its name or `contrast="high"` prop in typography components, the codemod will replace it with `"UPDATE_THIS_VALUE_WITH_A_NEW_COLOR_TOKEN"` string. You will have to discuss these instances with designers & manually update this value with a new color token that matches the contrast you need. + + ```diff + - Lorem ipsum + + Lorem ipsum + ``` + +- In a move towards internationalization, the default formatting of number in Amount component has now changed. It relies on locale state managed by [@razorpay/i18nify-js](https://www.npmjs.com/package/@razorpay/i18nify-js) library and fallbacks to browser locale to drive formatting. To maintain the previous formatting experience of the Amount component, ensure you follow the steps outlined in this [section](https://github.com/razorpay/blade/blob/master/packages/blade/upgrade-v11.md#amount). + +**Step 5**: Test your page and make sure everything works as expected. Once the migration is complete for all pages, you can remove the old version of Blade from your project. + +## Documentation + +By default, `blade.razorpay.com` will show documentation for the latest version of Blade. To view the documentation for an older version, you can use the version selector in the top-left corner of the page. + +Version Switcher + +## Available Rebranded Components + +To check out the list of available components, visit [Blade Component Status](https://blade.razorpay.com/?path=/docs/guides-component-status--docs). + +## Manual Migration Guide + +Only use this if you're unable to run the codemod described above. + +### Theme Tokens + +- **`paymentTheme` & `bankingTheme` have been removed. Use `bladeTheme` instead.** + + ```diff + import { BladeProvider } from '@razorpay/blade/components'; + - import { paymentTheme, bankingTheme } from "@razorpay/blade/tokens"; + + import { bladeTheme } from "@razorpay/blade/tokens"; + + const AppWrapper = () => { + return ( + - + + + + + ); + } + + export default AppWrapper; + ``` + +- **The `color` tokens have been updated. You may need to update your custom component styles to map with new tokens:** + + - [color tokens mapping](https://docs.google.com/spreadsheets/d/14p3QqubqkTe2K0701EYY_Ehia4fzFMOIzOo8exCguUA/edit#gid=877366376) + +- **The `font-size` and `line-height` tokens have been updated to a new scale. You may need to update your custom component styles to match the new scale:** + - [font-size scale](https://www.figma.com/file/5BZsOpNjbUHqgVh850yPBW/%5BResearch%5D-Typography-%26-Spacing-Refresh?type=design&node-id=244%3A188858&mode=design&t=vpFlyrSzO1jdpAPu-1) + - [line-height scale](https://www.figma.com/file/5BZsOpNjbUHqgVh850yPBW/%5BResearch%5D-Typography-%26-Spacing-Refresh?type=design&node-id=244%3A188858&mode=design&t=vpFlyrSzO1jdpAPu-1) + +### ActionList + +- **The `surfaceLevel` prop has been removed without replacement.** + + ```diff + - + + + ``` + +### Amount + +- **Amount component is now internationalized via [@razorpay/i18nify-js](https://www.npmjs.com/package/@razorpay/i18nify-js).** + +1. The `` component will now automatically format numbers based on the user's browser locale. For example, `` will render `₹1,23,456.79` for browsers with the `en-IN` default locale, whereas it will render `₹123,456.79` for browsers with the `en-US` locale. + +2. If you want to enable users to change the locale of your page, add the `@razorpay/i18nify-react` package and wrap your app inside the `I18nProvider`. Utilize the `setI18nState` utility to modify the locale. For more details, please refer to the [documentation](https://www.npmjs.com/package/@razorpay/i18nify-react). + +3. Additionally, if you prefer to maintain a fixed locale for your page and amount component, enclose your app within `..`. For more details, please refer to the [documentation](https://www.npmjs.com/package/@razorpay/i18nify-react). + +Example with `@razorpay/i18nify-react` + +```jsx +import React, { useEffect } from 'react'; +import { I18nProvider, useI18nContext } from '@razorpay/i18nify-react'; +import { BladeProvider, Amount } from '@razorpay/blade/components'; + +const ToggleAmount = ({ value }) => { + const { setI18nState } = useI18nContext(); + + function onLocaleChange() { + setI18nState({ locale: 'de-DE' }); + } + + return ( + <> + + + + ); +}; + +const App = () => { + return ( + + + + + + ); +}; +``` + +- **The accepted values for the `size` prop has been updated.** + + ```diff + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + ``` + +- **The `intent` prop has been removed in favor of the `color` prop.** + + ```diff + - + + + + - + + + + - + + + + - + + + ``` + +- **The `prefix` prop has been removed in favor of the new `currencyIndicator` prop.** + + ```diff + - + + + + - + + + ``` + +### Alert + +- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** + + ```diff + - + + + + - + + + ``` + +- **The `intent` prop has been removed in favor of the `color` prop.** + + ```diff + - + + + ``` + +### Typography Components + +- **The `Title` component has been removed in favor of the `Heading` component.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `type` prop has been removed from the `Text`, `Heading`, & `Display` components in favor of the `color` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + + - Hello + + Hello + + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `contrast` prop has been removed from the `Text`, `Heading`, & `Display` components.** + + ```diff + - Hello + + Hello + + // contrast="high" doesn't exist anymore, so you will need to manually update these cases a new color token that matches the contrast you need. + - Hello + + Hello + ``` + +- **The `variant` prop has been removed from the `Heading` component.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `size` prop scale has been updated in the `Heading` component** + + ```diff + - Hello + + Hello + + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `caption` variant of the `Text` component now accepts only `size="small"`.** + + ```diff + - Hello + + Hello + ``` + +- **The `weight` prop now accepts `"semibold"` instead of `"bold"` in the ` Text`, `Heading` , & `Display` components. The `Code` component continues to accept `"bold"`.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + + - Hello + + Hello + ``` + +### Badge + +- **The `fontWeight` prop has been removed.** + + ```diff + - Hello + + Hello + ``` + +- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `variant` prop has been removed in favor of the `color` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** + + ```diff + - Hello + + Hello + ``` + +### Button & Link + +- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** + + ```diff + - + + + + - Hello + + Hello + ``` + +### Card + +- **The `surfaceLevel` prop has been removed in favor of the new `backgroundColor` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + ``` + +- The `CardHeaderBadge`, `CardHeaderCounter`, `CardHeaderAmount`, `CardHeaderText`, `CardHeaderLink`, and `CardHeaderIconButton` components have the same changes as `Badge`, `Counter`, `Amount`, `Text`, `Link`, and `Button` components respectively. + +### Chip & ChipGroup + +- **The `intent` prop has been removed in favor of the `color` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + + - + + + + - + + + ``` + +### Counter + +- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `variant` prop has been removed in favor of the `color` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + ``` + +- **The `intent` prop has been removed in favor of the `color` prop.** + + ```diff + - Hello + + Hello + ``` + +- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** + + ```diff + - Hello + + Hello + ``` + +### Divider + +- **The `"normal"` value for the `variant` prop has been updated with new styles, if you need the old styles, use the `"muted"` value.** + + ```diff + - Hello + + Hello + ``` + +### Dropdown + +- **The `onDismiss` prop has been removed in favor of the `onOpenChange` prop.** + + ```diff + - console.log("Dismissed!!!)}> Hello + + { + + if (!isOpen) { + + console.log("Dismissed!!!"); + + } + + }} + + > + + - Hello + + { + + if (!isOpen) { + + handleDropdownDismiss(); + + } + + }} + + > + ``` + +### Indicator + +- **The `intent` prop has been removed in favor of the `color` prop.** + + ```diff + - + + + + - Hello + + Hello + ``` + +### IconButton + +- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** + + ```diff + - Hello + + Hello + + - Hello + + Hello + ``` + +### ProgressBar + +- **The `contrast` prop has been removed without replacement.** + + ```diff + - + + + ``` + +- **The `intent` prop has been removed in favor of the `color` prop.** + + ```diff + - + + + ``` + +### Skeleton + +- **The `contrast` prop has been removed without replacement.** + + ```diff + - + + + ``` + +### Spinner + +- **The `contrast` prop has been removed in favor of the `color` prop.** + + ```diff + - + + + + - + + + ``` + +- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** + + ```diff + - + + + ``` + +### Table + +- **The `surfaceLevel` prop has been removed without replacement.** + + ```diff + - + +
+ ``` diff --git a/packages/blade/docs/migration-docs/upgrade-v12.md b/packages/blade/docs/migration-docs/upgrade-v12.md new file mode 100644 index 00000000000..8b566f40821 --- /dev/null +++ b/packages/blade/docs/migration-docs/upgrade-v12.md @@ -0,0 +1,133 @@ +# Migration to Motion Presets (Blade v12) + +## Changes + +1. Token Structure for `motion.easing.*` tokens has changed + +We've changed the structure of motion easing tokens inorder to simplify the structure and have limited set of variations + +2. Motion React Setup + +Blade v12 introduces `motion` (prev `framer-motion`) as peer dependency and requires you to set it up in your projects. + +## Steps to Migrate + +- **Step 1:** Upgrade to latest `@razorpay/blade` package in your project +- **Step 2:** [Perform Tokens Changes](#token-changes) using Codemod or manually +- **Step 3:** Setup `motion/react` (or `framer-motion`) + +### Token Changes + +We have codemod to help you do the required token changes. You can run the codemod with following command (Replace `./PATH_TO_YOUR_DIR` with glob path of files / directories you want to migrate)- + +```sh +npx jscodeshift ./PATH_TO_YOUR_DIR --extensions=tsx,ts,jsx,js -t ./node_modules/@razorpay/blade/codemods/migrate-motion-tokens/transformers/index.ts --ignore-pattern="**/node_modules/** +``` + +
+ Manual Migration + +You can skip this if you've run the codemod but in case not or you see some edge cases, you can do manual migration by replacing the old tokens with equivalent new ones + +| Old Token | Equivalent New Token | +| -------------------------------------- | ------------------------------ | +| theme.motion.easing.entrance.effective | theme.motion.easing.entrance | +| theme.motion.easing.standard.effective | theme.motion.easing.standard | +| theme.motion.easing.exit.effective | theme.motion.easing.exit | +| theme.motion.easing.entrance.revealing | theme.motion.easing.entrance | +| theme.motion.easing.standard.revealing | theme.motion.easing.emphasized | +| theme.motion.easing.exit.revealing | theme.motion.easing.exit | +| theme.motion.easing.entrance.attentive | theme.motion.easing.overshoot | +| theme.motion.easing.exit.attentive | theme.motion.easing.exit | +| theme.motion.easing.standard.wary | theme.motion.easing.shake | + +
+ +### Motion React (Framer Motion) Setup + +> [!IMPORTANT] +> +> `framer-motion` library is now known as `motion/react` +> +> Checkout the [announcement by creator of framer-motion](https://bsky.app/profile/citizenofnowhe.re/post/3lar365ouuk2v) + +
+ Version Compatibility Note for consumers already using framer-motion with older version + +#### Version Compatibility Note for consumers already using framer-motion with older version + +We realised that several projects in razorpay are already using `framer-motion` and are on older versions. +To give some time to consumers to upgrade to framer-motion v11+, we'll be supporting framer-motion v4+ from blade. Although we will be dropping this support in next major version of blade so we recommend planning out framer-motion upgrade in coming quarter. + +- **If you're on React 18**, migrating to framer-motion v11 should be fairly simple and low-effort. Checkout [Migrating from framer-motion v4+ to motion/react v11+](#migrating-from-framer-motion-v4-to-motionreact-aka-framer-motion-v11) +- **For projects not on React 18 yet**, do plan out the upgrade soon to make sure future blade upgrades don't become blocker + +### Migrating from `framer-motion` v4+ to `motion/react` (aka `framer-motion` v11) + +1. Ensure you're on React 18 as `framer-motion` v7 makes React 18 a minimum supported version. + a. [Checkout React 18 upgrade guide](https://react.dev/blog/2022/03/08/react-18-upgrade-guide) or use [React's official codemod for upgrading](https://github.com/reactjs/react-codemod) + +2. `` -> `` + +These are mostly the changes you'll need if you're using core API. But if you're extensively using any utilities / internal functions, checkout the full changelog of framer-motion here- https://motion.dev/docs/react-upgrade-guide + +
+ +- #### Install `motion + + ```sh + yarn add `motion` --dev + ``` + +- #### Setup reduced bundle version of `motion/react + + ##### If you're only using basic presets like `Fade`, `Move`, `Slide`, `Stagger`, `AnimateInteractions`, etc + + ```ts + // features.js + import { domAnimation } from 'motion/react'; + export default domAnimation; // ~15kb + ``` + + ##### If you're using `Morph` or Layout animations of Motion React + + ```ts + // features.js + import { domMax } from 'motion/react'; + export default domMax; // ~25kb (This includes domAnimation bundle as well so no need to import domAnimation again) + ``` + + ##### Lazy load into your App.js + + ```tsx + import { LazyMotion } from 'motion/react'; + import { m } from 'motion'; + + // Make sure to return the specific export containing the feature bundle. + const loadFeatures = () => import('./features.js').then((res) => res.default); + + function App() { + return ( + // `strict` ensures that you only use `m` and not `motion` in your components + // Blade presets always use `m` while animating + + {/* The animations run when loadFeatures resolves. */} + + + ); + } + ``` + + ##### Go ahead and enjoy the Blade Motion Presets + + ```ts + import { Fade, Badge } from '@razorpay/blade/components'; + + function MyComponent() { + return ( + + Motion Approved + + ); + } + ``` diff --git a/packages/blade/docs/tokens/Motion.stories.mdx b/packages/blade/docs/tokens/Motion.stories.mdx index d0dbb33e526..ec3ccb70395 100644 --- a/packages/blade/docs/tokens/Motion.stories.mdx +++ b/packages/blade/docs/tokens/Motion.stories.mdx @@ -23,7 +23,7 @@ import MovingDiv from '../components/MovingDiv'; }} /> -# 🎬 Motion +# 🎬 Motion Tokens > These tokens should be used along with the [makeMotionTime util](?path=/story/utils-makemotiontime--docs). @@ -54,43 +54,14 @@ export const Motion = () => {

Easing

-

standard

- {Object.entries(theme.motion.easing.standard).map(([key, value]) => ( + {Object.entries(theme.motion.easing).map(([key, value]) => ( - + - - ))} - -
{`theme.motion.easing.standard.${key}`}{`theme.motion.easing.${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]) => ( - - - - ))} diff --git a/packages/blade/src/components/AnimateInteractions/AnimateInteractions.stories.tsx b/packages/blade/src/components/AnimateInteractions/AnimateInteractions.stories.tsx index e5cbcb4d771..5141c174fcf 100644 --- a/packages/blade/src/components/AnimateInteractions/AnimateInteractions.stories.tsx +++ b/packages/blade/src/components/AnimateInteractions/AnimateInteractions.stories.tsx @@ -3,28 +3,25 @@ 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 { Heading, Text } from '~components/Typography'; import { Card, CardBody } from '~components/Card'; +import { ExternalLinkIcon } from '~components/Icons'; +import { Scale } from '~components/Scale'; +import { AnimateInteractionsSandbox } from '~components/BaseMotion/docs/codeExamples'; const Page = (): React.ReactElement => { return ( Usage - - {` - const todo = 'todo'; - `} - + ); }; @@ -42,16 +39,15 @@ export default { const AnimateInteractionsTemplate: StoryFn = (args) => { return ( - - - {args.children} - + + ); }; export const Default = AnimateInteractionsTemplate.bind({}); Default.args = { + motionTriggers: ['hover'], children: ( Hover me for magic @@ -62,34 +58,129 @@ Default.args = { ), }; -export const ScaleChildOnCardHover = AnimateInteractionsTemplate.bind({}); -ScaleChildOnCardHover.args = { +export const AnimateChildOnCardHover = AnimateInteractionsTemplate.bind({}); +AnimateChildOnCardHover.args = { + motionTriggers: ['hover'], + children: ( + + + + + + Payment Pages + + + Accept payments{' '} + + without coding on a custom branded store + + + + Hover over this card to see how AnimateInteractions component helps in animating child + based on interactions on parent + + + + + + + + + + + + + ), +}; + +export const ShowOverlayOnImageHover = AnimateInteractionsTemplate.bind({}); +ShowOverlayOnImageHover.args = { + motionTriggers: ['hover'], + children: ( + + Hubble Takes a Look at Tangled Galaxies + + + + + + + ), +}; + +export const ScaleOnParentHoverAndFocus = AnimateInteractionsTemplate.bind({}); +ScaleOnParentHoverAndFocus.args = { + motionTriggers: ['hover', 'focus'], children: ( {}} > - - - - Hi I am text inside card. Hover over this card to see magic - - - - - - - + + + + NASA Hubble Gallery + + + + + + Hover over this card or press tab and focus on it to see how AnimateInteractions + component helps in animating child based on interactions on parent + - + + + + Hubble Takes a Look at Tangled Galaxies + + + diff --git a/packages/blade/src/components/AnimateInteractions/AnimateInteractions.web.tsx b/packages/blade/src/components/AnimateInteractions/AnimateInteractions.web.tsx index 87ef5d5a201..20520e401c3 100644 --- a/packages/blade/src/components/AnimateInteractions/AnimateInteractions.web.tsx +++ b/packages/blade/src/components/AnimateInteractions/AnimateInteractions.web.tsx @@ -5,7 +5,33 @@ import React from 'react'; import { useAnimation } from 'framer-motion'; import type { AnimateInteractionsProps } from './types'; -export const AnimateInteractions = ({ +/** + * ## AnimateInteractions + * + * AnimateInteractions is the utility preset that we offer to help you animate children when the parent is interacted. + * + * This is in a way equivalent to following CSS- + * ```css + * .parent:hover .child { } + * ``` + * + * ### Usage + * + * ```jsx + * // <-- When this is hovered + * + * // <-- this animates in + * + * + * + * + * ``` + */ +const AnimateInteractions = ({ children, motionTriggers = ['hover'], }: AnimateInteractionsProps) => { @@ -29,3 +55,5 @@ export const AnimateInteractions = ({ ); }; + +export { AnimateInteractions }; diff --git a/packages/blade/src/components/AnimateInteractions/types.ts b/packages/blade/src/components/AnimateInteractions/types.ts index 2af742e13ea..f935317c600 100644 --- a/packages/blade/src/components/AnimateInteractions/types.ts +++ b/packages/blade/src/components/AnimateInteractions/types.ts @@ -2,5 +2,27 @@ import type { BaseMotionBoxProps } from '~components/BaseMotion'; export type AnimateInteractionsProps = { children: React.ReactElement; + + /** + * AnimateInteractions is a component meant to give you triggers that animate children inside of it. + * + * So the motionTriggers you apply here will be applied on AnimateInteractions and children will animate based on that. + * + * E.g. + * + * ```jsx + * // <-- When this is hovered + * + * // <-- this animates in + * + * + * + * + * ``` + */ motionTriggers?: BaseMotionBoxProps['motionTriggers']; }; diff --git a/packages/blade/src/components/BaseMotion/BaseMotion.tsx b/packages/blade/src/components/BaseMotion/BaseMotion.tsx index 21caeffa110..1f3590f3991 100644 --- a/packages/blade/src/components/BaseMotion/BaseMotion.tsx +++ b/packages/blade/src/components/BaseMotion/BaseMotion.tsx @@ -7,9 +7,17 @@ import { useStagger } from '~components/Stagger/StaggerProvider'; import type { BladeElementRef } from '~utils/types'; import type { BaseMotionBoxProps, MotionMeta, BaseMotionEntryExitProps } from './types'; import { makeAnimationVariables, useMotionVariants } from './baseMotionUtils'; +import { useMemoizedStyles } from '~components/Box/BaseBox/useMemoizedStyles'; +import type { BoxProps } from '~components/Box'; +import type { Theme } from '~components/BladeProvider'; // Creating empty styled component so that the final component supports `as` prop -const StyledDiv = styled.div``; +const StyledDiv = styled.div((props: BoxProps & { theme: Theme }) => { + // We're turning normal div into Box here because our BaseBox does not forward the props to next component which is needed for enhancer component wrapper + const boxStyles = useMemoizedStyles(props); + return boxStyles; +}); + const MotionDiv = motion(StyledDiv); const _BaseMotionBox = ( @@ -40,8 +48,16 @@ const _BaseMotionBox = ( isInsideStaggerContainer ? staggerType : type, ); + console.count('BaseMotionBox'); + return ( - + {children} ); diff --git a/packages/blade/src/components/BaseMotion/baseMotionUtils.ts b/packages/blade/src/components/BaseMotion/baseMotionUtils.ts index 5f3c4f7b890..efa11144854 100644 --- a/packages/blade/src/components/BaseMotion/baseMotionUtils.ts +++ b/packages/blade/src/components/BaseMotion/baseMotionUtils.ts @@ -1,3 +1,4 @@ +import { useReducedMotion } from 'framer-motion'; import type { BaseMotionBoxProps, MotionTriggersType, MotionVariantsType } from './types'; // This type is exported in new framer-motion versions but does not exist in earlier versions so adding it manually here diff --git a/packages/blade/src/components/BaseMotion/docs/MotionDashboardComponents.web.tsx b/packages/blade/src/components/BaseMotion/docs/MotionDashboardComponents.web.tsx new file mode 100644 index 00000000000..825b344d0cd --- /dev/null +++ b/packages/blade/src/components/BaseMotion/docs/MotionDashboardComponents.web.tsx @@ -0,0 +1,375 @@ +import React from 'react'; +import { Link, matchPath, Route, Switch, useLocation } from 'react-router-dom'; +import styled from 'styled-components'; +import { TopNav, TopNavActions, TopNavContent, TopNavBrand, TabNavItems } from '~components/TopNav'; +import type { TabNavItemProps } from '~components/TopNav'; +import { TabNav, TabNavItem } from '~components/TopNav'; +import { Box } from '~components/Box'; +import type { SideNavLinkProps, SideNavProps } from '~components/SideNav'; +import { + SideNav, + SideNavBody, + SideNavLevel, + SideNavLink, + SideNavSection, +} from '~components/SideNav'; +import { + SearchIcon, + AcceptPaymentsIcon, + ShoppingBagIcon, + ActivityIcon, + AnnouncementIcon, + HomeIcon, + LayoutIcon, + PaymentButtonIcon, + PaymentGatewayIcon, + PaymentLinkIcon, + PaymentPagesIcon, + RazorpayxPayrollIcon, +} from '~components/Icons'; +import { RazorpayLogo } from '~components/SideNav/docs/RazorpayLogo'; +import { SearchInput } from '~components/Input/SearchInput'; +import { Button } from '~components/Button'; +import { Tooltip } from '~components/Tooltip'; +import { Avatar } from '~components/Avatar'; +import { Heading, Text } from '~components/Typography'; +import { Menu, MenuHeader, MenuItem, MenuOverlay } from '~components/Menu'; +import { Link as BladeLink } from '~components/Link'; + +import { makeSize, useBreakpoint, useTheme } from '~utils'; +import { + SIDE_NAV_EXPANDED_L1_WIDTH_XL, + SIDE_NAV_EXPANDED_L1_WIDTH_BASE, +} from '~components/SideNav/tokens'; +import BaseBox from '~components/Box/BaseBox'; +import { AnimatePresence } from 'motion/react'; +import { Slide } from '~components/Slide'; +import { Move } from '~components/Move'; +import { Spinner } from '~components/Spinner'; + +const isItemActive = ( + location: { pathname: string }, + { href, activeOnLinks }: { href?: string; activeOnLinks?: string[] }, +): boolean => { + const isCurrentPathActive = Boolean( + matchPath(location.pathname, { + path: href, + exact: false, + }), + ); + + const isSubItemActive = Boolean( + activeOnLinks?.find((href) => matchPath(location.pathname, { path: href, exact: false })), + ); + + return isCurrentPathActive || isSubItemActive; +}; + +const NavLink = ( + props: Omit & { + activeOnLinks?: string[]; + }, +): React.ReactElement => { + const location = useLocation(); + + return ( + + ); +}; + +const SideNavExample = ({ + isOpen, + onDismiss, +}: Pick): React.ReactElement => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const TabNavItemLink = React.forwardRef< + HTMLAnchorElement, + Omit & { + activeOnLinks?: string[]; + } +>((props, ref) => { + const location = useLocation(); + return ( + + ); +}); + +const DashboardBackground = styled.div(() => { + return { + height: '100vh', + background: 'radial-gradient(94.74% 64.44% at 29.03% 15.17%, #FFFFFF 0%, #90A5BB 100%)', + }; +}); + +export const DashboardWithRoutingExample = ({ + routeComponent, +}: { + routeComponent: (props: any) => React.ReactElement; +}) => { + const [isLoading, setIsLoading] = React.useState(true); + + React.useEffect(() => { + setTimeout(() => { + setIsLoading(false); + }, 4000); + }, []); + + const { theme } = useTheme(); + const { matchedBreakpoint, matchedDeviceType } = useBreakpoint({ + breakpoints: theme.breakpoints, + }); + const location = useLocation(); + const isTablet = matchedBreakpoint === 'm'; + const isMobile = matchedDeviceType === 'mobile'; + const [isSideBarOpen, setIsSideBarOpen] = React.useState(false); + + return ( + <> + + + + + + + {isLoading ? null : ( + + + + + {isMobile ? ( + <> + + Home + + + Payments + + + + + + + + + + John Doe + + + Razorpay Trusted Merchant + + + + + Settings + + + Logout + + + + + ) : ( + <> + + + + + + {({ items }) => { + return ( + <> + + {items.map((item) => { + return ( + + ); + })} + + + ); + }} + + + + {isTablet ? ( + + + + + ); +}; diff --git a/packages/blade/src/components/BaseMotion/docs/MotionRecipes.stories.tsx b/packages/blade/src/components/BaseMotion/docs/MotionRecipes.stories.tsx new file mode 100644 index 00000000000..7a149f61f19 --- /dev/null +++ b/packages/blade/src/components/BaseMotion/docs/MotionRecipes.stories.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import type { StoryFn } from '@storybook/react'; +import StoryRouter from 'storybook-react-router'; +import { Code, Text } from '~components/Typography'; +import { Card, CardBody, CardHeader, CardHeaderLeading } from '~components/Card'; +import { DashboardWithRoutingExample } from './MotionDashboardComponents.web'; +import { Move } from '~components/Move'; + +export default { + title: 'Motion/Recipes', + // eslint-disable-next-line babel/new-cap + decorators: [StoryRouter(undefined, { initialEntries: ['/app/home'] })] as unknown, + args: { + motionTriggers: ['mount'], + type: 'inout', + shouldUnmountWhenHidden: false, + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, + parameters: { + docs: { + page: null, + }, + }, +}; + +const DashboardRoute = ({ + match, +}: { + match: { + params: { + id: string; + }; + }; +}) => { + return ( + + + + + + + + This is an example of Move component used for Page + Transition. + + + + + ); +}; + +export const DashboardMotionRecipe: StoryFn<() => React.ReactElement> = (props) => { + return ; +}; diff --git a/packages/blade/src/components/BaseMotion/docs/StepperRouterExample.web.tsx b/packages/blade/src/components/BaseMotion/docs/StepperRouterExample.web.tsx new file mode 100644 index 00000000000..db24383c75c --- /dev/null +++ b/packages/blade/src/components/BaseMotion/docs/StepperRouterExample.web.tsx @@ -0,0 +1,78 @@ +import { Route, useHistory, useLocation, matchPath, Switch } from 'react-router-dom'; +import type { StepItemProps } from '~components/StepGroup'; +import { StepGroup, StepItem, StepItemIndicator } from '~components/StepGroup'; +import { AnimatePresence } from 'motion/react'; +import { Box } from '~components/Box'; +import React from 'react'; + +const StepperRouterExample = ({ + routeComponent, + stepsSampleData, +}: { + routeComponent: (_props: any) => React.ReactElement; + stepsSampleData: StepItemProps[]; +}): React.ReactElement => { + const history = useHistory(); + const navigateTo = (e: React.MouseEvent, url: string) => { + e.preventDefault(); + history.push(url); + }; + + const location = useLocation(); + + const getSelectedItemIndex = (pathname: string) => { + return stepsSampleData.findIndex((stepInfo) => matchPath(pathname, stepInfo.href!)); + }; + + return ( + + + + {stepsSampleData.map((stepInfo, index) => { + const stepPathname = stepInfo.href!.replace('/onboarding/', ''); + const selectedIndex = getSelectedItemIndex(location.pathname); + + const isBeforeSelectedItem = index < selectedIndex; + const isSelectedItem = index === selectedIndex; + + return ( + + } + onClick={(e) => navigateTo(e, stepPathname)} + stepProgress={isSelectedItem ? 'start' : isBeforeSelectedItem ? 'full' : 'none'} + {...stepInfo} + /> + ); + })} + + + + + + + + + + + + ); +}; + +export { StepperRouterExample }; diff --git a/packages/blade/src/components/BaseMotion/docs/codeExamples.tsx b/packages/blade/src/components/BaseMotion/docs/codeExamples.tsx new file mode 100644 index 00000000000..4cad8d45e59 --- /dev/null +++ b/packages/blade/src/components/BaseMotion/docs/codeExamples.tsx @@ -0,0 +1,281 @@ +import { BoxProps } from '~components/Box'; +import { Sandbox } from '~utils/storybook/Sandbox'; + +export const FadeSandbox = ({ padding }: { padding?: BoxProps['padding'] }) => { + return ( + {`import React from 'react'; + import { + Fade, + Badge, + InfoIcon, + Box, + Button + } from '@razorpay/blade/components'; + + function App() { + const [isVisible, setIsVisible] = React.useState(true); + + return ( + + + + + Boop + + + + ) + } + + export default App; + `} + ); +}; + +export const MoveSandbox = ({ padding }: { padding?: BoxProps['padding'] }) => { + return ( + {`import React from 'react'; + import { + Move, + Card, + CardBody, + Text, + Box, + Button + } from '@razorpay/blade/components'; + + function App() { + const [isVisible, setIsVisible] = React.useState(true); + + return ( + + + + + + Card that animates + + + + + ) + } + + export default App; + `} + ); +}; + +export const SlideSandbox = ({ padding }: { padding?: BoxProps['padding'] }) => { + return ( + {`import React from 'react'; + import { + Slide, + Card, + CardBody, + Text, + Box, + Button + } from '@razorpay/blade/components'; + + function App() { + const [isVisible, setIsVisible] = React.useState(true); + + return ( + + + + + + Card that animates + + + + + ) + } + + export default App; + `} + ); +}; + +export const ScaleSandbox = ({ padding }: { padding?: BoxProps['padding'] }) => { + return ( + {`import { + Scale, + Card, + CardBody, + Text, + Box, + } from '@razorpay/blade/components'; + + function App() { + return ( + + + + + Card that scales on hover + + + + + ) + } + + export default App; + `} + ); +}; + +export const MorphSandbox = ({ padding }: { padding?: BoxProps['padding'] }) => { + return ( + + {` + import { Morph, Box, TextInput } from '@razorpay/blade/components'; + + const App = () => { + const [showNameButton, setShowNameButton] = React.useState(true); + + return ( + + {showNameButton ? ( + + + + ) : ( + + + setShowNameButton(true)} variant="button"> + Submit + + } + /> + + + )} + + ) + } + + export default App; + `} + + ); +}; + +export const AnimateInteractionsSandbox = ({ padding }: { padding?: BoxProps['padding'] }) => { + return ( + + {` + import { + AnimateInteractions, + Move, + Card, + CardBody, + Box, + Heading, + Text, + Button, + ExternalLinkIcon, + } from '@razorpay/blade/components'; + + function App(): React.ReactElement { + return ( + + + + + + + Payment Pages + + + Accept payments{' '} + + without coding on a custom branded store + + + + Hover over this card to see how AnimateInteractions component helps in animating child + based on interactions on parent + + + + + + + + + + + + + + ) + } + + export default App; + `} + + ); +}; + +export const StaggerSandbox = () => { + return ( + + {` + import React from 'react'; + import { + Stagger, + Move, + ChipGroup, + Chip, + Box, + Button + } from '@razorpay/blade/components'; + + function App() { + const [isVisible, setIsVisible] = React.useState(true); + + return ( + + + + + {[ + 'Business Type: Freelance', + 'Account Status: Activated', + 'Test Mode: Disabled', + 'Primary Product: Banking', + ].map((chipLabel) => { + return ( + + {chipLabel} + + ); + })} + + + + ) + } + + export default App; + `} + + ); +}; diff --git a/packages/blade/src/components/BaseMotion/types.ts b/packages/blade/src/components/BaseMotion/types.ts index 1aa3ca228db..d2850729fca 100644 --- a/packages/blade/src/components/BaseMotion/types.ts +++ b/packages/blade/src/components/BaseMotion/types.ts @@ -22,12 +22,28 @@ type MotionDelay = keyof Delay | { enter: keyof Delay; exit: keyof Delay }; type BaseMotionBoxProps = { as?: React.ReactElement; children: React.ReactElement; + + /** + * Whether component should animate in, animate out, or animate both in and out + * + * With type="in", component will only animate in but immediately be removed on exit without animation + * With type="out", component will only animate out but immediately mount on enter without animation + * With type="inout", component animates in and out both + * + * @default 'inout' + */ type?: 'in' | 'out' | 'inout'; + /** * @default ['mount'] */ motionTriggers?: MotionTriggersType[]; + + /** + * This internally maps to `variants` of motion/react + */ motionVariants?: MotionVariantsType; + /** * Option to override the animate * @@ -55,9 +71,72 @@ type BaseMotionBoxProps = { }; type BaseMotionEntryExitProps = Pick & { + /** + * Handle visibility of motion component. + * + * By default components animate on mount but if you want to mount/unmount them, use this prop instead + * + * ### ❌ Incorrect way to handle visibility of components + * + * ```jsx + * isVisible ? : null + * ``` + * + * ### ✅ Correct way + * + * ```jsx + * + * ``` + * + * This prop allows us to handle exit animations before the component unmounts + */ isVisible?: boolean; + + /** + * ### Usage + * + * ```jsx + * + * I appear when the component is in view of the scroll + * + * ``` + * + * Values: + * - mount: Component animates when it mounts + * - in-view: Component animates when its in view of the scroll + * - focus: Component animates in when its in focus + * - on-animate-interactions: Component animates based on motionTriggers of component + * + * @default ['mount'] + */ motionTriggers?: MotionTriggerEntryExitType[]; + + /** + * By default components are only made opacity: 0. When this prop is set to true, components will unmount and be removed from the page. + * + * **Warn:** Setting this true might cause layout shifts in your page since component will be removed so do check it once and add minHeight to its container + * + * @default false + */ shouldUnmountWhenHidden?: boolean; + + /** + * Handles delay of animations + * + * ## Usage + * + * ```jsx + * + * ``` + * + * ### Different delays for enter and exit + * + * ```jsx + * + * ``` + * + * @default undefined + */ delay?: MotionDelay; }; diff --git a/packages/blade/src/components/Box/BaseBox/types/propsTypes.ts b/packages/blade/src/components/Box/BaseBox/types/propsTypes.ts index 6e848a938f5..524b5d21c90 100644 --- a/packages/blade/src/components/Box/BaseBox/types/propsTypes.ts +++ b/packages/blade/src/components/Box/BaseBox/types/propsTypes.ts @@ -201,7 +201,10 @@ type BaseBoxVisualProps = MakeObjectResponsive< // Visual props that are specific to Box type BoxVisualProps = MakeObjectResponsive<{ - backgroundColor: BackgroundColorString<'surface'> | 'transparent'; + backgroundColor: + | BackgroundColorString<'surface'> + | BackgroundColorString<'overlay'> + | 'transparent'; }> & { // Intentionally keeping this outside of MakeObjectResponsive since we only want as to be string and not responsive object // styled-components do not support passing `as` prop as an object diff --git a/packages/blade/src/components/Box/Box.tsx b/packages/blade/src/components/Box/Box.tsx index a7a97a0cb67..eb025f3449b 100644 --- a/packages/blade/src/components/Box/Box.tsx +++ b/packages/blade/src/components/Box/Box.tsx @@ -13,10 +13,11 @@ const validateBackgroundString = (stringBackgroundColorValue: string): void => { if ( !stringBackgroundColorValue.startsWith('surface.background') && !stringBackgroundColorValue.startsWith('brand.') && + !stringBackgroundColorValue.startsWith('overlay.') && stringBackgroundColorValue !== 'transparent' ) { throwBladeError({ - message: `Oops! Currently you can only use \`transparent\`, \`surface.background.*\`, and \`brand.*\` tokens with backgroundColor property but we received \`${stringBackgroundColorValue}\` instead.\n\n Do you have a usecase of using other values? Create an issue on https://github.com/razorpay/blade repo to let us know and we can discuss ✨`, + message: `Oops! Currently you can only use \`transparent\`, \`surface.background.*\`, \`overlay.*\` and \`brand.*\` tokens with backgroundColor property but we received \`${stringBackgroundColorValue}\` instead.\n\n Do you have a usecase of using other values? Create an issue on https://github.com/razorpay/blade repo to let us know and we can discuss ✨`, moduleName: 'Box', }); } diff --git a/packages/blade/src/components/Fade/Fade.native.tsx b/packages/blade/src/components/Fade/Fade.native.tsx index 5ad0e4c2efe..52b73b5f223 100644 --- a/packages/blade/src/components/Fade/Fade.native.tsx +++ b/packages/blade/src/components/Fade/Fade.native.tsx @@ -1,8 +1,9 @@ import { BaseMotionEntryExitProps } from '~components/BaseMotion'; import { Text } from '~components/Typography'; import { throwBladeError } from '~utils/logger'; +import type { FadeProps } from './types'; -const Fade = (_props: BaseMotionEntryExitProps): React.ReactElement => { +const Fade = (_props: FadeProps): React.ReactElement => { throwBladeError({ message: 'Fade is not yet implemented for native', moduleName: 'Fade', diff --git a/packages/blade/src/components/Fade/Fade.stories.tsx b/packages/blade/src/components/Fade/Fade.stories.tsx index 74cb66ef901..c8263f2083b 100644 --- a/packages/blade/src/components/Fade/Fade.stories.tsx +++ b/packages/blade/src/components/Fade/Fade.stories.tsx @@ -1,28 +1,46 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { Title } from '@storybook/addon-docs'; +import StoryRouter from 'storybook-react-router'; import { Fade } from './'; import type { FadeProps } from './'; -import { Sandbox } from '~utils/storybook/Sandbox'; import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; import { Button } from '~components/Button'; import { Box } from '~components/Box'; import { InternalCardExample } from '../Card/Card.stories'; import { TextInput } from '~components/Input/TextInput'; +import { Code, Heading, Text } from '~components/Typography'; +import { Chip, ChipGroup } from '~components/Chip'; +import { StepperRouterExample } from '~components/BaseMotion/docs/StepperRouterExample'; +import { Card, CardBody, CardHeader, CardHeaderLeading } from '~components/Card'; +import { StepItemProps } from '~components/StepGroup'; +import { Alert } from '~components/Alert'; +import { Link } from '~components/Link'; +import { FadeSandbox } from '~components/BaseMotion/docs/codeExamples'; const Page = (): React.ReactElement => { return ( + + Make sure you've followed the installation and setup of Motion React from our{' '} + Installation Doc (Step 3) + + } + isDismissible={false} + isFullWidth + /> + Usage - - {` - const todo = 'todo'; - `} - + ); }; @@ -31,6 +49,20 @@ export default { title: 'Motion/Fade', component: Fade, tags: ['autodocs'], + // eslint-disable-next-line babel/new-cap + decorators: [StoryRouter(undefined, { initialEntries: ['/onboarding/introduction'] })] as unknown, + args: { + motionTriggers: ['mount'], + type: 'inout', + shouldUnmountWhenHidden: false, + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, parameters: { docs: { page: Page, @@ -42,11 +74,17 @@ const FadeTemplate: StoryFn = (args) => { const [isVisible, setIsVisible] = React.useState(true); return ( - + - + ); }; @@ -56,16 +94,167 @@ Default.args = { children: , }; -export const InitialFade = () => { +export const WithDifferentComponents = (args: typeof Fade): React.ReactElement => { + const [isVisible, setIsVisible] = React.useState(true); + + return ( + + + + + + + + + + + + + + + Box that fades + + + + + Heading that fades + + + + + {['Public', 'Small Business', 'Large Organization'].map((chipValue: string) => ( + + {chipValue} + + ))} + + + + +

Fade with custom components. Ensure you forward refs to your custom components

+
+
+
+ ); +}; + +export const FadeWhenInView = (args: typeof Fade): React.ReactElement => { + return ( + + + Scroll down + + + + + + ); +}; + +FadeWhenInView.args = { + motionTriggers: ['in-view'], +}; + +const stepsSampleData: StepItemProps[] = [ + { + title: 'Introduction', + timestamp: 'Mon, 15th Oct’23 | 12:00pm', + description: 'Introduction to Razorpay Payment Gateway', + href: '/onboarding/introduction', + }, + { + title: 'Personal Details', + timestamp: 'Mon, 16th Oct’23 | 12:00pm', + description: 'Fill your Personal Details for onboarding', + href: '/onboarding/personal-details', + }, + { + title: 'Business Details', + timestamp: 'Mon, 17th Oct’23 | 12:00pm', + description: 'Fill your Business Details for onboarding', + href: '/onboarding/business-details', + }, + { + title: 'Complete Onboarding', + timestamp: 'Mon, 20th Oct’23 | 12:00pm', + description: 'Complete your onboarding to start', + href: '/onboarding/complete-onboarding', + }, +]; + +const OnboardingRoute = ({ + match, +}: { + match: { + params: { + id: string; + }; + }; +}) => { + const stepDataIndex = stepsSampleData.findIndex((stepInfo) => + stepInfo.href?.includes(match.params.id), + ); + const stepData = stepsSampleData[stepDataIndex]; + + if (!stepData) { + return ( + + Unknown Route + + ); + } + return ( - + + + + + + + {stepData.href ?? ''} + + + + This is an example of Fade component used for Page + Transition. + + + ); }; +export const OnRouteChange: StoryFn<(props: typeof Fade) => React.ReactElement> = (props) => { + return ( + + ); +}; + export const WithRef = (args: typeof Fade): React.ReactElement => { - const [isVisible, setIsVisible] = React.useState(true); + const [isVisible, setIsVisible] = React.useState(false); const inputRef = React.useRef(null); React.useEffect(() => { diff --git a/packages/blade/src/components/Fade/Fade.web.tsx b/packages/blade/src/components/Fade/Fade.web.tsx index d55e52f45ca..75c79732f39 100644 --- a/packages/blade/src/components/Fade/Fade.web.tsx +++ b/packages/blade/src/components/Fade/Fade.web.tsx @@ -1,13 +1,35 @@ import React from 'react'; import { BaseMotionEntryExit } from '~components/BaseMotion'; -import type { BaseMotionEntryExitProps, MotionVariantsType } from '~components/BaseMotion'; +import type { MotionVariantsType } from '~components/BaseMotion'; import { castWebType, useTheme } from '~utils'; import { cssBezierToArray } from '~utils/cssBezierToArray'; import { msToSeconds } from '~utils/msToSeconds'; +import type { FadeProps } from './types'; -export type FadeProps = BaseMotionEntryExitProps; - -export const Fade = ({ +/** + * ## Fade + * + * Fade is one of the motion presets that we expose from blade to help you fade in / fade out components easily. + * + * ### Usage + * + * #### Fade in on mount + * + * ```jsx + * + * + * + * ``` + * + * #### Conditionally fade based on state + * + * ```jsx + * + * + * + * ``` + */ +const Fade = ({ children, isVisible, type = 'inout', @@ -54,3 +76,5 @@ export const Fade = ({ /> ); }; + +export { Fade }; diff --git a/packages/blade/src/components/Fade/index.ts b/packages/blade/src/components/Fade/index.ts index 9407d81011d..f557451b354 100644 --- a/packages/blade/src/components/Fade/index.ts +++ b/packages/blade/src/components/Fade/index.ts @@ -1,2 +1,2 @@ export { Fade } from './Fade'; -export type { FadeProps } from './Fade'; +export type { FadeProps } from './types'; diff --git a/packages/blade/src/components/Fade/types.ts b/packages/blade/src/components/Fade/types.ts new file mode 100644 index 00000000000..720d4bca79d --- /dev/null +++ b/packages/blade/src/components/Fade/types.ts @@ -0,0 +1,6 @@ +import type { BaseMotionEntryExitProps } from '~components/BaseMotion'; + +export type FadeProps = Pick< + BaseMotionEntryExitProps, + 'children' | 'isVisible' | 'motionTriggers' | 'shouldUnmountWhenHidden' | 'type' | 'delay' +>; diff --git a/packages/blade/src/components/FileUpload/FileUpload.web.tsx b/packages/blade/src/components/FileUpload/FileUpload.web.tsx index 9fc07d5cba0..8cb5b3e11fa 100644 --- a/packages/blade/src/components/FileUpload/FileUpload.web.tsx +++ b/packages/blade/src/components/FileUpload/FileUpload.web.tsx @@ -27,7 +27,6 @@ import { formHintLeftLabelMarginLeft } from '~components/Input/BaseInput/baseInp import { useMergeRefs } from '~utils/useMergeRefs'; import { useControllableState } from '~utils/useControllable'; import { getInnerMotionRef, getOuterMotionRef } from '~utils/getMotionRefs'; -import { fireNativeEvent } from '~utils/fireNativeEvent'; const _FileUpload: React.ForwardRefRenderFunction = ( { diff --git a/packages/blade/src/components/Morph/Morph.stories.tsx b/packages/blade/src/components/Morph/Morph.stories.tsx index f7f1fdc442e..c205ff75fd3 100644 --- a/packages/blade/src/components/Morph/Morph.stories.tsx +++ b/packages/blade/src/components/Morph/Morph.stories.tsx @@ -3,27 +3,38 @@ import type { Meta, StoryFn } from '@storybook/react'; import { Title } from '@storybook/addon-docs'; import { Morph } from './'; import type { MorphProps } from './'; -import { Sandbox } from '~utils/storybook/Sandbox'; import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; import { Button } from '~components/Button'; import { Box } from '~components/Box'; import { AnimatePresence } from 'framer-motion'; import { TextInput } from '~components/Input/TextInput'; import { Link } from '~components/Link'; +import { Display, Heading, Text } from '~components/Typography'; +import { Card, CardBody } from '~components/Card'; +import { MorphSandbox } from '~components/BaseMotion/docs/codeExamples'; +import { CloseIcon, SearchIcon } from '~components/Icons'; +import { SearchInput } from '~components/Input/SearchInput'; +import { IconButton } from '~components/Button/IconButton'; +import { Modal, ModalBody } from '~components/Modal'; +import { Alert } from '~components/Alert'; const Page = (): React.ReactElement => { return ( + Usage - - {` - const todo = 'todo'; - `} - + ); }; @@ -45,13 +56,14 @@ const MorphTemplate: StoryFn = (args) => { {showNameButton ? ( - + ) : ( - + = (args) => { }; export const Default = MorphTemplate.bind({}); +Default.args = { + layoutId: 'button-to-input-morph', +}; + +export const MorphOnText = (args: MorphProps): React.ReactElement => { + const [showPage, setShowPage] = React.useState(false); + + return ( + + + {showPage ? ( + + + + Payment Pages + + + + + + Welcome to payment pages! + + + + ) : ( + + setShowPage(true)} width="300px"> + + + Payment Pages + + + + + Payment Pages allow you to build pages without writing any code. Click this card + to know more (and see some motion magic) + + + + + + )} + + + ); +}; + +MorphOnText.args = { + layoutId: 'card-heading', +}; + +export const MorphBorderRadius = (): React.ReactElement => { + const [showPage, setShowPage] = React.useState(false); + + return ( + + + + {showPage ? ( + + + + ) : ( + + + + )} + + + ); +}; + +export const DangerousButton = (): React.ReactElement => { + const [shouldConfirm, setShouldConfirm] = React.useState(false); + + return ( + + + {shouldConfirm ? ( + + + + ) : ( + + + + )} + + + ); +}; diff --git a/packages/blade/src/components/Morph/Morph.web.tsx b/packages/blade/src/components/Morph/Morph.web.tsx index b6bc0057d68..2da08a3cbb3 100644 --- a/packages/blade/src/components/Morph/Morph.web.tsx +++ b/packages/blade/src/components/Morph/Morph.web.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { MotionDiv } from '~components/BaseMotion'; import { useMemoizedStyles } from '~components/Box/BaseBox/useMemoizedStyles.web'; -import { useTheme } from '~utils'; +import { castWebType, useTheme } from '~utils'; +import { cssBezierToArray } from '~utils/cssBezierToArray'; +import { msToSeconds } from '~utils/msToSeconds'; import { MorphProps } from './types'; const Morph = ({ children, layoutId }: MorphProps) => { @@ -18,6 +20,10 @@ const Morph = ({ children, layoutId }: MorphProps) => { layoutId={layoutId} // The animations that are not supported by layout animation are animated using `animate` prop here animate={cssProps} + transition={{ + duration: msToSeconds(theme.motion.duration.moderate), + ease: cssBezierToArray(castWebType(theme.motion.easing.standard)), + }} /> ); }; diff --git a/packages/blade/src/components/Move/Move.native.tsx b/packages/blade/src/components/Move/Move.native.tsx index 80900869269..a0214bdcf33 100644 --- a/packages/blade/src/components/Move/Move.native.tsx +++ b/packages/blade/src/components/Move/Move.native.tsx @@ -1,8 +1,8 @@ -import { BaseMotionEntryExitProps } from '~components/BaseMotion'; import { Text } from '~components/Typography'; import { throwBladeError } from '~utils/logger'; +import type { MoveProps } from './types'; -const Move = (_props: BaseMotionEntryExitProps): React.ReactElement => { +const Move = (_props: MoveProps): React.ReactElement => { throwBladeError({ message: 'Move is not yet implemented for native', moduleName: 'Move', @@ -12,4 +12,3 @@ const Move = (_props: BaseMotionEntryExitProps): React.ReactElement => { }; export { Move }; -export type { BaseMotionEntryExitProps as MoveProps }; diff --git a/packages/blade/src/components/Move/Move.stories.tsx b/packages/blade/src/components/Move/Move.stories.tsx index d0aadc27d4e..63bbb499757 100644 --- a/packages/blade/src/components/Move/Move.stories.tsx +++ b/packages/blade/src/components/Move/Move.stories.tsx @@ -1,41 +1,31 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { Title } from '@storybook/addon-docs'; -import { Move } from '.'; -import type { MoveProps } from '.'; -import { Sandbox } from '~utils/storybook/Sandbox'; +import StoryRouter from 'storybook-react-router'; +import { Move } from './'; +import type { MoveProps } from './'; import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; import { Button } from '~components/Button'; import { Box } from '~components/Box'; import { InternalCardExample } from '../Card/Card.stories'; -import { Text } from '~components/Typography'; -import { - Card, - CardBody, - CardFooter, - CardFooterTrailing, - CardHeader, - CardHeaderBadge, - CardHeaderCounter, - CardHeaderIcon, - CardHeaderLeading, - CardHeaderTrailing, -} from '~components/Card'; -import { CheckCircleIcon } from '~components/Icons'; +import { TextInput } from '~components/Input/TextInput'; +import { Code, Heading, Text } from '~components/Typography'; +import { Chip, ChipGroup } from '~components/Chip'; +import { StepperRouterExample } from '~components/BaseMotion/docs/StepperRouterExample'; +import { Card, CardBody, CardHeader, CardHeaderLeading } from '~components/Card'; +import { StepItemProps } from '~components/StepGroup'; +import { MoveSandbox } from '~components/BaseMotion/docs/codeExamples'; const Page = (): React.ReactElement => { return ( Usage - - {` - const todo = 'todo'; - `} - + ); }; @@ -44,6 +34,20 @@ export default { title: 'Motion/Move', component: Move, tags: ['autodocs'], + // eslint-disable-next-line babel/new-cap + decorators: [StoryRouter(undefined, { initialEntries: ['/onboarding/introduction'] })] as unknown, + args: { + motionTriggers: ['mount'], + type: 'inout', + shouldUnmountWhenHidden: false, + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, parameters: { docs: { page: Page, @@ -53,8 +57,15 @@ export default { const MoveTemplate: StoryFn = (args) => { const [isVisible, setIsVisible] = React.useState(true); + return ( - + @@ -67,3 +78,189 @@ export const Default = MoveTemplate.bind({}); Default.args = { children: , }; + +export const WithDifferentComponents = (args: typeof Move): React.ReactElement => { + const [isVisible, setIsVisible] = React.useState(true); + + return ( + + + + + + + + + + + + + + + Box that moves + + + + + Heading that moves + + + + + {['Public', 'Small Business', 'Large Organization'].map((chipValue: string) => ( + + {chipValue} + + ))} + + + + +

Move with custom components. Ensure you forward refs to your custom components

+
+
+
+ ); +}; + +export const MoveWhenInView = (args: typeof Move): React.ReactElement => { + return ( + + + Scroll down + + + + + + ); +}; + +MoveWhenInView.args = { + motionTriggers: ['in-view'], +}; + +const stepsSampleData: StepItemProps[] = [ + { + title: 'Introduction', + timestamp: 'Mon, 15th Oct’23 | 12:00pm', + description: 'Introduction to Razorpay Payment Gateway', + href: '/onboarding/introduction', + }, + { + title: 'Personal Details', + timestamp: 'Mon, 16th Oct’23 | 12:00pm', + description: 'Fill your Personal Details for onboarding', + href: '/onboarding/personal-details', + }, + { + title: 'Business Details', + timestamp: 'Mon, 17th Oct’23 | 12:00pm', + description: 'Fill your Business Details for onboarding', + href: '/onboarding/business-details', + }, + { + title: 'Complete Onboarding', + timestamp: 'Mon, 20th Oct’23 | 12:00pm', + description: 'Complete your onboarding to start', + href: '/onboarding/complete-onboarding', + }, +]; + +const OnboardingRoute = ({ + match, +}: { + match: { + params: { + id: string; + }; + }; +}) => { + const stepDataIndex = stepsSampleData.findIndex((stepInfo) => + stepInfo.href?.includes(match.params.id), + ); + const stepData = stepsSampleData[stepDataIndex]; + + if (!stepData) { + return ( + + Unknown Route + + ); + } + + return ( + + + + + + + + {stepData.href ?? ''} + + + + This is an example of Move component used for Page + Transition. + + + + + ); +}; + +export const OnRouteChange: StoryFn<(props: typeof Move) => React.ReactElement> = (props) => { + return ( + + ); +}; + +export const WithRef = (args: typeof Move): React.ReactElement => { + const [isVisible, setIsVisible] = React.useState(false); + + const inputRef = React.useRef(null); + React.useEffect(() => { + if (isVisible) { + inputRef.current?.focus(); + } + }, [isVisible]); + + return ( + + + + + + + ); +}; diff --git a/packages/blade/src/components/Move/Move.web.tsx b/packages/blade/src/components/Move/Move.web.tsx index 8ee1a0a9169..44f282de101 100644 --- a/packages/blade/src/components/Move/Move.web.tsx +++ b/packages/blade/src/components/Move/Move.web.tsx @@ -1,13 +1,35 @@ -import { BaseMotionEntryExit } from '~components/BaseMotion'; -import type { BaseMotionEntryExitProps, MotionVariantsType } from '~components/BaseMotion'; import React from 'react'; +import { BaseMotionEntryExit } from '~components/BaseMotion'; +import type { MotionVariantsType } from '~components/BaseMotion'; import { msToSeconds } from '~utils/msToSeconds'; import { cssBezierToArray } from '~utils/cssBezierToArray'; import { castWebType, makeSpace, useTheme } from '~utils'; +import type { MoveProps } from './types'; -export type MoveProps = BaseMotionEntryExitProps; - -export const Move = ({ +/** + * ## Move + * + * Move is one of the motion presets that we expose from blade to help you make components appear / disappear with move + * + * ### Usage + * + * #### Move in on mount + * + * ```jsx + * + * + * + * ``` + * + * #### Conditionally move based on state + * + * ```jsx + * + * + * + * ``` + */ +const Move = ({ children, type = 'inout', isVisible, @@ -59,3 +81,5 @@ export const Move = ({ /> ); }; + +export { Move }; diff --git a/packages/blade/src/components/Move/index.ts b/packages/blade/src/components/Move/index.ts index b6de64f645f..a72801567d2 100644 --- a/packages/blade/src/components/Move/index.ts +++ b/packages/blade/src/components/Move/index.ts @@ -1,2 +1,2 @@ export { Move } from './Move'; -export type { MoveProps } from './Move'; +export type { MoveProps } from './types'; diff --git a/packages/blade/src/components/Move/types.ts b/packages/blade/src/components/Move/types.ts new file mode 100644 index 00000000000..3776a6be307 --- /dev/null +++ b/packages/blade/src/components/Move/types.ts @@ -0,0 +1,8 @@ +import type { BaseMotionEntryExitProps } from '~components/BaseMotion'; + +type MoveProps = Pick< + BaseMotionEntryExitProps, + 'children' | 'isVisible' | 'motionTriggers' | 'shouldUnmountWhenHidden' | 'type' | 'delay' +>; + +export type { MoveProps }; diff --git a/packages/blade/src/components/Scale/Scale.stories.tsx b/packages/blade/src/components/Scale/Scale.stories.tsx index 064e465d5d3..edfb4d20d24 100644 --- a/packages/blade/src/components/Scale/Scale.stories.tsx +++ b/packages/blade/src/components/Scale/Scale.stories.tsx @@ -3,25 +3,23 @@ import type { Meta, StoryFn } from '@storybook/react'; import { Title } from '@storybook/addon-docs'; import { Scale } from './'; import type { ScaleProps } from './'; -import { Sandbox } from '~utils/storybook/Sandbox'; import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; import { Button } from '~components/Button'; import { Box } from '~components/Box'; import { InternalCardExample } from '../Card/Card.stories'; +import qrCodesImage from './docs-qrcodes.svg'; +import { ScaleSandbox } from '~components/BaseMotion/docs/codeExamples'; const Page = (): React.ReactElement => { return ( Usage - - {` - const todo = 'todo'; - `} - + ); }; @@ -44,7 +42,9 @@ const ControlledScaleTemplate: StoryFn = (args) => { - + + +
); }; @@ -55,10 +55,14 @@ const ScaleTemplate: StoryFn = (args) => { export const Default = ScaleTemplate.bind({}); Default.args = { - children: , + children: QR Codes Image, }; export const Controlled = ControlledScaleTemplate.bind({}); Controlled.args = { - children: , + children: ( + + + + ), }; diff --git a/packages/blade/src/components/Scale/Scale.web.tsx b/packages/blade/src/components/Scale/Scale.web.tsx index 2a66492fbd8..b2be7179e55 100644 --- a/packages/blade/src/components/Scale/Scale.web.tsx +++ b/packages/blade/src/components/Scale/Scale.web.tsx @@ -6,7 +6,38 @@ import { cssBezierToArray } from '~utils/cssBezierToArray'; import { castWebType, useTheme } from '~utils'; import type { ScaleProps } from './types'; -export const Scale = ({ +/** + * ## Scale + * + * Scale is one of the highlight presets that we expose from blade to help you scale up or scale down components based on interactions + * + * ### Usage + * + * #### Scale up on hover + * + * ```jsx + * + * + * + * ``` + * + * #### Scale down on tap + * + * ```jsx + * + * + * + * ``` + * + * #### Conditionally scale + * + * ```jsx + * + * + * + * ``` + */ +const Scale = ({ children, isHighlighted, type = 'inout', @@ -18,7 +49,9 @@ export const Scale = ({ const { theme } = useTheme(); const fadeVariants: MotionVariantsType = { - initial: {}, + initial: { + scale: 1, + }, animate: { scale: isHighlighted || !isControlledHighlighted @@ -31,7 +64,9 @@ export const Scale = ({ ease: cssBezierToArray(castWebType(theme.motion.easing.standard)), }, }, - exit: {}, + exit: { + scale: 1, + }, }; return ( @@ -43,3 +78,5 @@ export const Scale = ({ /> ); }; + +export { Scale }; diff --git a/packages/blade/src/components/Scale/docs-qrcodes.svg b/packages/blade/src/components/Scale/docs-qrcodes.svg new file mode 100644 index 00000000000..31b2291e668 --- /dev/null +++ b/packages/blade/src/components/Scale/docs-qrcodes.svg @@ -0,0 +1 @@ + diff --git a/packages/blade/src/components/Scale/types.ts b/packages/blade/src/components/Scale/types.ts index 46be65de8db..6f7c02f53d5 100644 --- a/packages/blade/src/components/Scale/types.ts +++ b/packages/blade/src/components/Scale/types.ts @@ -1,11 +1,20 @@ import type { BaseMotionBoxProps } from '~components/BaseMotion'; -type ScaleProps = { +type ScaleProps = Pick & { + /** + * Controlled state of scaling. If you want to scale up on hover / focus, etc, checkout `motionTriggers` prop instead. + * + * This is when you want to scale up / scale down conditionally + * + * With `isHighlighted={true}`, your component will be in scaled state + * With `isHighlighted={false}`, your component will be in normal state + */ isHighlighted?: boolean; + + /** + * Whether to scale up or scale down the component + */ variant?: 'scale-up' | 'scale-down'; - type?: BaseMotionBoxProps['type']; - motionTriggers?: BaseMotionBoxProps['motionTriggers']; - children: BaseMotionBoxProps['children']; }; export type { ScaleProps }; diff --git a/packages/blade/src/components/Slide/Slide.stories.tsx b/packages/blade/src/components/Slide/Slide.stories.tsx index 98e88d0bf1f..7134ff038c2 100644 --- a/packages/blade/src/components/Slide/Slide.stories.tsx +++ b/packages/blade/src/components/Slide/Slide.stories.tsx @@ -1,27 +1,31 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { Title } from '@storybook/addon-docs'; -import { Slide } from '.'; -import type { SlideProps } from '.'; -import { Sandbox } from '~utils/storybook/Sandbox'; +import StoryRouter from 'storybook-react-router'; +import { Slide } from './'; +import type { SlideProps } from './'; import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; import { Button } from '~components/Button'; import { Box } from '~components/Box'; import { InternalCardExample } from '../Card/Card.stories'; +import { TextInput } from '~components/Input/TextInput'; +import { Code, Heading, Text } from '~components/Typography'; +import { Chip, ChipGroup } from '~components/Chip'; +import { StepperRouterExample } from '~components/BaseMotion/docs/StepperRouterExample'; +import { Card, CardBody, CardHeader, CardHeaderLeading } from '~components/Card'; +import { StepItemProps } from '~components/StepGroup'; +import { SlideSandbox } from '~components/BaseMotion/docs/codeExamples'; const Page = (): React.ReactElement => { return ( Usage - - {` - const todo = 'todo'; - `} - + ); }; @@ -30,6 +34,21 @@ export default { title: 'Motion/Slide', component: Slide, tags: ['autodocs'], + // eslint-disable-next-line babel/new-cap + decorators: [StoryRouter(undefined, { initialEntries: ['/onboarding/introduction'] })] as unknown, + args: { + motionTriggers: ['mount'], + type: 'inout', + shouldUnmountWhenHidden: false, + direction: 'bottom', + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, parameters: { docs: { page: Page, @@ -41,17 +60,220 @@ const SlideTemplate: StoryFn = (args) => { const [isVisible, setIsVisible] = React.useState(true); return ( - + - + + + ); }; export const Default = SlideTemplate.bind({}); Default.args = { - children: , - direction: { enter: 'bottom', exit: 'left' }, + direction: 'bottom', +}; + +export const WithDifferentDirections = SlideTemplate.bind({}); +WithDifferentDirections.args = { + direction: { enter: 'right', exit: 'bottom' }, +}; + +export const WithDifferentComponents = (args: typeof Slide): React.ReactElement => { + const [isVisible, setIsVisible] = React.useState(true); + + return ( + + + + + + + + + + + + + + + Box that slides + + + + + Heading that slides + + + + + {['Public', 'Small Business', 'Large Organization'].map((chipValue: string) => ( + + {chipValue} + + ))} + + + + +

Slide with custom components. Ensure you forward refs to your custom components

+
+
+
+ ); +}; + +export const SlideWhenInView = (args: typeof Slide): React.ReactElement => { + return ( + + + Scroll down + + + + + + ); +}; + +SlideWhenInView.args = { + motionTriggers: ['in-view'], + direction: 'right', +}; + +const stepsSampleData: StepItemProps[] = [ + { + title: 'Introduction', + timestamp: 'Mon, 15th Oct’23 | 12:00pm', + description: 'Introduction to Razorpay Payment Gateway', + href: '/onboarding/introduction', + }, + { + title: 'Personal Details', + timestamp: 'Mon, 16th Oct’23 | 12:00pm', + description: 'Fill your Personal Details for onboarding', + href: '/onboarding/personal-details', + }, + { + title: 'Business Details', + timestamp: 'Mon, 17th Oct’23 | 12:00pm', + description: 'Fill your Business Details for onboarding', + href: '/onboarding/business-details', + }, + { + title: 'Complete Onboarding', + timestamp: 'Mon, 20th Oct’23 | 12:00pm', + description: 'Complete your onboarding to start', + href: '/onboarding/complete-onboarding', + }, +]; + +const OnboardingRoute = ({ + match, +}: { + match: { + params: { + id: string; + }; + }; +}) => { + const stepDataIndex = stepsSampleData.findIndex((stepInfo) => + stepInfo.href?.includes(match.params.id), + ); + const stepData = stepsSampleData[stepDataIndex]; + + if (!stepData) { + return ( + + Unknown Route + + ); + } + + return ( + + + + + + + + {stepData.href ?? ''} + + + + This is an example of Slide component used for Page + Transition. + + + + + ); +}; + +export const OnRouteChange = (): React.ReactElement => { + return ( + + ); +}; + +OnRouteChange.args = { + direction: { enter: 'bottom', exit: 'top' }, +}; + +export const WithRef = (args: typeof Slide): React.ReactElement => { + const [isVisible, setIsVisible] = React.useState(false); + + const inputRef = React.useRef(null); + React.useEffect(() => { + if (isVisible) { + inputRef.current?.focus(); + } + }, [isVisible]); + + return ( + + + + + + + ); }; diff --git a/packages/blade/src/components/Slide/Slide.web.tsx b/packages/blade/src/components/Slide/Slide.web.tsx index 87ca052d452..2c69b385f9a 100644 --- a/packages/blade/src/components/Slide/Slide.web.tsx +++ b/packages/blade/src/components/Slide/Slide.web.tsx @@ -25,7 +25,32 @@ const getFromTransform = ( return `translateY(${fromOffset})`; }; -export const Slide = ({ +/** + * ## Slide + * + * Slide is one of the motion presets that we expose from blade to help you make components slide in from outside of viewport + * + * If you're looking for subtle movement on enter in the viewport itself, checkout `Move` preset instead. + * + * ### Usage + * + * #### Slide in on mount + * + * ```jsx + * + * + * + * ``` + * + * #### Conditionally slide based on state + * + * ```jsx + * + * + * + * ``` + */ +const Slide = ({ children, type = 'inout', direction = 'bottom', @@ -118,3 +143,5 @@ export const Slide = ({ /> ); }; + +export { Slide }; diff --git a/packages/blade/src/components/Slide/types.ts b/packages/blade/src/components/Slide/types.ts index e3c14b4d4a4..150dea9fef5 100644 --- a/packages/blade/src/components/Slide/types.ts +++ b/packages/blade/src/components/Slide/types.ts @@ -2,7 +2,23 @@ import type { BaseMotionEntryExitProps } from '~components/BaseMotion'; type SlideDirections = 'top' | 'right' | 'bottom' | 'left'; -export type SlideProps = BaseMotionEntryExitProps & { +type SlideProps = Pick< + BaseMotionEntryExitProps, + 'children' | 'isVisible' | 'motionTriggers' | 'shouldUnmountWhenHidden' | 'type' | 'delay' +> & { + /** + * Direction from where the component should slide + * + * ```jsx + * // Slides from top on enter and slides to top on exit + * + * + * // Slides from right on enter, slides to top on exit + * + * ``` + * + * @default 'bottom' + */ direction?: | SlideDirections | { @@ -10,5 +26,21 @@ export type SlideProps = BaseMotionEntryExitProps & { exit: SlideDirections; }; + /** + * Offset from which the component should slide. + * + * Possible values are `100vh`, `100vw`, `${number}%`. + * + * The Slide component is only meant to be used to animate something from outside of your viewport. + * So this prop has limited values to ensure it gives you control on what outside of viewport means in your case yet restrict unintentional usage of moving in something from middle of viewport + * + * If you want subtle movement on enter, `Move` is probably the component you're looking for. + * + * @default + * `direction="right | left"` -> 100vw + * `direction="top | bottom" -> 100vh + */ fromOffset?: `100vh` | `100vw` | `${number}%`; }; + +export type { SlideProps }; diff --git a/packages/blade/src/components/Stagger/Stagger.stories.tsx b/packages/blade/src/components/Stagger/Stagger.stories.tsx index 7acfd1eb13f..7b5c01362b9 100644 --- a/packages/blade/src/components/Stagger/Stagger.stories.tsx +++ b/packages/blade/src/components/Stagger/Stagger.stories.tsx @@ -1,29 +1,32 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { Title } from '@storybook/addon-docs'; +import StoryRouter from 'storybook-react-router'; import { Stagger } from './'; import type { StaggerProps } from './'; -import { Sandbox } from '~utils/storybook/Sandbox'; import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; import { Button } from '~components/Button'; import { Box } from '~components/Box'; import { InternalCardExample } from '../Card/Card.stories'; import { Fade } from '~components/Fade'; import { Move } from '~components/Move'; +import { Slide } from '~components/Slide'; +import { Card, CardBody, CardHeader, CardHeaderLeading } from '~components/Card'; +import { Text } from '~components/Typography'; +import { StepperRouterExample } from '~components/BaseMotion/docs/StepperRouterExample'; +import type { StepItemProps } from '~components/StepGroup'; +import { ChipGroup } from '~components/Chip'; +import { Chip } from '~components/Chip'; +import { StaggerSandbox } from '~components/BaseMotion/docs/codeExamples'; const Page = (): React.ReactElement => { return ( Usage - - {` - const todo = 'todo'; - `} - + ); }; @@ -32,6 +35,20 @@ export default { title: 'Motion/Stagger', component: Stagger, tags: ['autodocs'], + // eslint-disable-next-line babel/new-cap + decorators: [StoryRouter(undefined, { initialEntries: ['/onboarding/introduction'] })] as unknown, + args: { + motionTriggers: ['mount'], + type: 'inout', + shouldUnmountWhenHidden: false, + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + }, parameters: { docs: { page: Page, @@ -42,11 +59,23 @@ export default { const StaggerTemplate: StoryFn = (args) => { const [isVisible, setIsVisible] = React.useState(true); return ( - + - + {args.children} @@ -56,7 +85,7 @@ const StaggerTemplate: StoryFn = (args) => { export const Default = StaggerTemplate.bind({}); Default.args = { children: ( - + <> @@ -66,14 +95,14 @@ Default.args = { - + ), }; export const MoveStagger = StaggerTemplate.bind({}); MoveStagger.args = { children: ( - + <> @@ -83,6 +112,124 @@ MoveStagger.args = { - + + ), +}; + +export const SlideStagger = StaggerTemplate.bind({}); +SlideStagger.args = { + children: ( + <> + + + + + + + + + + ), }; + +export const OnMount = () => { + return ( + + + + + + + + + + + + ); +}; + +const stepsSampleData: StepItemProps[] = [ + { + title: 'Introduction', + timestamp: 'Mon, 15th Oct’23 | 12:00pm', + description: 'Introduction to Razorpay Payment Gateway', + href: '/onboarding/introduction', + }, + { + title: 'Personal Details', + timestamp: 'Mon, 16th Oct’23 | 12:00pm', + description: 'Fill your Personal Details for onboarding', + href: '/onboarding/personal-details', + }, + { + title: 'Business Details', + timestamp: 'Mon, 17th Oct’23 | 12:00pm', + description: 'Fill your Business Details for onboarding', + href: '/onboarding/business-details', + }, + { + title: 'Complete Onboarding', + timestamp: 'Mon, 20th Oct’23 | 12:00pm', + description: 'Complete your onboarding to start', + href: '/onboarding/complete-onboarding', + }, +]; + +const OnboardingRoute = ({ + match, +}: { + match: { + params: { + id: string; + }; + }; +}) => { + const stepDataIndex = stepsSampleData.findIndex((stepInfo) => + stepInfo.href?.includes(match.params.id), + ); + const stepData = stepsSampleData[stepDataIndex]; + + if (!stepData) { + return ( + + Unknown Route + + ); + } + + return ( + + + + + + + + {[ + 'Business Type: Freelance', + 'Account Status: Activated', + 'Test Mode: Disabled', + 'Primary Product: Banking', + ].map((chipLabel) => { + return ( + + {chipLabel} + + ); + })} + + + + + ); +}; + +export const OnRouteChange: StoryFn<(props: typeof Move) => React.ReactElement> = (props) => { + return ( + + ); +}; diff --git a/packages/blade/src/components/Stagger/Stagger.web.tsx b/packages/blade/src/components/Stagger/Stagger.web.tsx index e4d8fbc01e8..0071d8b0e60 100644 --- a/packages/blade/src/components/Stagger/Stagger.web.tsx +++ b/packages/blade/src/components/Stagger/Stagger.web.tsx @@ -7,15 +7,50 @@ import React from 'react'; import { msToSeconds } from '~utils/msToSeconds'; import { useTheme } from '~utils'; -export const Stagger = ({ +/** + * ## Stagger + * + * Stagger is a utility motion preset we expose from blade to help you implement stagger animations. + * + * You can use any of the base entry / exit presets like `Fade`, `Move`, `Slide` inside of it and the components will appear one after the other instead of all at once. + * + * **Note:** Unlike other motion presets, Stagger is not an enhancer components and renders a Box instead. You can use BoxProps on this component. + * + * ### Usage + * + * #### Move with Stagger + * + * ```jsx + * + * + * + * + * + * ``` + * + * #### Conditionally make the parent visible or invisible + * + * `isVisible` prop in this case should be on Stagger instead of children preset component + * + * ```jsx + * + * + * + * + * + * ``` + * + */ +const Stagger = ({ children, isVisible = true, type = 'inout', shouldUnmountWhenHidden = false, delay = 'none', + motionTriggers, + ...boxProps }: StaggerProps) => { const { theme } = useTheme(); - // Only need AnimatePresence when we have to unmount the component const AnimateWrapper = shouldUnmountWhenHidden ? AnimatePresence : React.Fragment; // keep it always mounted when shouldUnmountWhenHidden is false @@ -46,9 +81,11 @@ export const Stagger = ({ {children} @@ -58,3 +95,5 @@ export const Stagger = ({ ); }; + +export { Stagger }; diff --git a/packages/blade/src/components/Stagger/types.ts b/packages/blade/src/components/Stagger/types.ts index 5854eedfd0c..e1e1e996a47 100644 --- a/packages/blade/src/components/Stagger/types.ts +++ b/packages/blade/src/components/Stagger/types.ts @@ -1,7 +1,11 @@ import type { BaseMotionEntryExitProps } from '~components/BaseMotion'; +import { BoxProps } from '~components/Box'; -type StaggerProps = BaseMotionEntryExitProps & { +type StaggerProps = Pick< + BaseMotionEntryExitProps, + 'isVisible' | 'motionTriggers' | 'shouldUnmountWhenHidden' | 'type' | 'delay' +> & { children: React.ReactElement[] | React.ReactElement; -}; +} & Omit; export type { StaggerProps }; diff --git a/packages/blade/src/components/Tabs/Tabs.web.tsx b/packages/blade/src/components/Tabs/Tabs.web.tsx index b887f583e47..d1078e752d3 100644 --- a/packages/blade/src/components/Tabs/Tabs.web.tsx +++ b/packages/blade/src/components/Tabs/Tabs.web.tsx @@ -7,36 +7,6 @@ import BaseBox from '~components/Box/BaseBox'; import { metaAttribute, MetaConstants } from '~utils/metaAttribute'; import { BladeElementRef } from '~utils/types'; -/** - * ### Tabs - * - * Check out the [Tab Stories & Examples](https://blade.razorpay.com/?path=/docs/components-tabs--default) - * - * ---- - * ### Basic Usage - * - * ```jsx - * import { Tabs, TabList, TabItem, TabPanel } from '@razorpay/blade/components'; - * - * - * - * Subscription - * Plans - * Settings - * - * - * - * Subscriptions Panel - * - * - * Plans Panel - * - * - * Settings Panel - * - * - * ``` - */ const _Tabs = ( { children, @@ -98,6 +68,36 @@ const _Tabs = ( ); }; +/** + * ### Tabs + * + * Check out the [Tab Stories & Examples](https://blade.razorpay.com/?path=/docs/components-tabs--default) + * + * ---- + * ### Basic Usage + * + * ```jsx + * import { Tabs, TabList, TabItem, TabPanel } from '@razorpay/blade/components'; + * + * + * + * Subscription + * Plans + * Settings + * + * + * + * Subscriptions Panel + * + * + * Plans Panel + * + * + * Settings Panel + * + * + * ``` + */ const Tabs = React.forwardRef(_Tabs); export { Tabs }; diff --git a/packages/blade/src/components/index.ts b/packages/blade/src/components/index.ts index 09224d8a038..a6287c8df10 100644 --- a/packages/blade/src/components/index.ts +++ b/packages/blade/src/components/index.ts @@ -22,6 +22,7 @@ export * from './Divider'; export * from './Drawer'; export * from './DatePicker'; export * from './Dropdown'; +export * from './Fade'; export * from './FileUpload'; export * from './Icons'; export * from './Indicator'; @@ -36,16 +37,20 @@ export * from './Link'; export * from './List'; export * from './LiveAnnouncer'; export * from './Modal'; +export * from './Move'; +export * from './Morph'; export * from './Menu'; export * from './Popover'; export * from './ProgressBar'; export * from './Radio'; +export * from './Scale'; export * from './SideNav'; export * from './Skeleton'; export * from './SkipNav'; export * from './Spinner'; export * from './SpotlightPopoverTour'; export * from './StepGroup'; +export * from './Slide'; export * from './Switch'; export * from './Table'; export * from './Tabs'; diff --git a/packages/blade/src/utils/storybook/Sandbox/StackblitzEditor/Sandbox.web.tsx b/packages/blade/src/utils/storybook/Sandbox/StackblitzEditor/Sandbox.web.tsx index d555e168fe4..2687050e637 100644 --- a/packages/blade/src/utils/storybook/Sandbox/StackblitzEditor/Sandbox.web.tsx +++ b/packages/blade/src/utils/storybook/Sandbox/StackblitzEditor/Sandbox.web.tsx @@ -13,6 +13,7 @@ import { tsConfigJSON, viteConfigTS, vitePackageJSON, + featuresJS, } from '../baseCode'; import type { SandboxStackBlitzProps } from '../types'; import BaseBox from '~components/Box/BaseBox'; @@ -68,6 +69,7 @@ const useStackblitzSetup = ({ }), 'App.tsx': code ? dedent(code) : '', 'Logger.tsx': logger, + 'features.js': featuresJS, 'vite.config.ts': viteConfigTS, 'tsconfig.json': tsConfigJSON, 'package.json': vitePackageJSON, diff --git a/packages/blade/src/utils/storybook/Sandbox/baseCode.ts b/packages/blade/src/utils/storybook/Sandbox/baseCode.ts index 16c2fba6480..af1a002e69b 100644 --- a/packages/blade/src/utils/storybook/Sandbox/baseCode.ts +++ b/packages/blade/src/utils/storybook/Sandbox/baseCode.ts @@ -55,6 +55,7 @@ export const getReactScriptsJSDependencies = (): Dependencies => { react: '^18', 'react-dom': '^18', 'react-scripts': '4.0.3', + motion: '11.12.0', '@razorpay/blade': getBladeVersion(), 'styled-components': packageJson.peerDependencies['styled-components'], '@razorpay/i18nify-js': packageJson.peerDependencies['@razorpay/i18nify-js'], @@ -69,6 +70,7 @@ const getViteReactTSDependencies = (): Dependencies => { react: '^18', 'react-dom': '^18', 'react-router-dom': '^6', + motion: '11.12.0', '@types/react': '^18', '@types/react-dom': '^18', '@razorpay/blade': getBladeVersion(), @@ -99,6 +101,12 @@ export const vitePackageJSON = JSON.stringify( 4, ); +export const featuresJS = dedent`// features.js +import { domMax } from 'motion/react'; +// ~25kb (Only expose domAnimations instead of domMax if you're not using Morph preset or layout animations in your project) +export default domMax; +`; + export const viteConfigTS = dedent` import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' @@ -234,6 +242,9 @@ export const getIndexTSX = ({ }): string => dedent` import { createRoot } from "react-dom/client"; import { createGlobalStyle } from "styled-components"; +import { LazyMotion } from 'motion/react'; + +const loadFeatures = () => import('./features.js').then((res) => res.default); import { BladeProvider, Box } from "@razorpay/blade/components"; import { ${themeTokenName}, createTheme } from "@razorpay/blade/tokens"; @@ -273,18 +284,20 @@ const getTheme = () => { root.render( - - - + + + + + + ${showConsole ? '' : ''} - ${showConsole ? '' : ''} - + ); diff --git a/packages/blade/upgrade-v11.md b/packages/blade/upgrade-v11.md index acc79a89770..64c2a62d7be 100644 --- a/packages/blade/upgrade-v11.md +++ b/packages/blade/upgrade-v11.md @@ -1,536 +1,3 @@ # Upgrade Guide for v11 (Brand Refresh) -## Upgrade Workflow Overview - -All the rebranding upgrade activity starts at the design end and is then followed by engineering - -Upgrade Workflow Overview - -## Migration with Codemod - -**Step 1:** Install this version of Blade as `yarn add @razorpay/blade-rebranded@npm:@razorpay/blade@v11.0.0`. - -**Step 2:** Install new fonts (Inter & Tasa) by following [this file](https://blade.razorpay.com/?path=/docs/guides-installation--docs#-installing-fonts). - -**Step 3:** The codemod will update the components to the new version of Blade. Execute the codemod on the file/directory that needs to be migrated for the page via the following command: - -> Need help? Check out [jscodeshift docs](https://github.com/facebook/jscodeshift) for CLI usage tips. - -```sh -npx jscodeshift ./PATH_TO_YOUR_DIR --extensions=tsx,ts,jsx,js -t ./node_modules/@razorpay/blade-rebranded/codemods/brand-refresh/transformers/index.ts --ignore-pattern="**/node_modules/**" -``` - -### 🚧 Watch Out for Limitations & Edge Cases - -> [!IMPORTANT] -> -> There might be some situations where the codemod falls short. If you encounter errors, handle those cases manually by following up with your designer. - -- The codemod doesn't handle the migration of conditionally rendered props. Take a moment to manually inspect and update such cases. The codemod will also log a warning for such cases with the line number & path to the file. For example: `Expression found in the "size" attribute, please update manually: src/pages/ResumeWithRazorpay/sections/WhyResumeSection.tsx:20` - - ```diff - - Hello - + Hello - ``` - -- With Blade v11, we have removed `highContrast` & `lowContrast` terminology from color tokens. If you have used any color token which has `highContrast` in its name or `contrast="high"` prop in typography components, the codemod will replace it with `"UPDATE_THIS_VALUE_WITH_A_NEW_COLOR_TOKEN"` string. You will have to discuss these instances with designers & manually update this value with a new color token that matches the contrast you need. - - ```diff - - Lorem ipsum - + Lorem ipsum - ``` - -- In a move towards internationalization, the default formatting of number in Amount component has now changed. It relies on locale state managed by [@razorpay/i18nify-js](https://www.npmjs.com/package/@razorpay/i18nify-js) library and fallbacks to browser locale to drive formatting. To maintain the previous formatting experience of the Amount component, ensure you follow the steps outlined in this [section](https://github.com/razorpay/blade/blob/master/packages/blade/upgrade-v11.md#amount). - -**Step 5**: Test your page and make sure everything works as expected. Once the migration is complete for all pages, you can remove the old version of Blade from your project. - -## Documentation - -By default, `blade.razorpay.com` will show documentation for the latest version of Blade. To view the documentation for an older version, you can use the version selector in the top-left corner of the page. - -Version Switcher - -## Available Rebranded Components - -To check out the list of available components, visit [Blade Component Status](https://blade.razorpay.com/?path=/docs/guides-component-status--docs). - -## Manual Migration Guide - -Only use this if you're unable to run the codemod described above. - -### Theme Tokens - -- **`paymentTheme` & `bankingTheme` have been removed. Use `bladeTheme` instead.** - - ```diff - import { BladeProvider } from '@razorpay/blade/components'; - - import { paymentTheme, bankingTheme } from "@razorpay/blade/tokens"; - + import { bladeTheme } from "@razorpay/blade/tokens"; - - const AppWrapper = () => { - return ( - - - + - - - ); - } - - export default AppWrapper; - ``` - -- **The `color` tokens have been updated. You may need to update your custom component styles to map with new tokens:** - - - [color tokens mapping](https://docs.google.com/spreadsheets/d/14p3QqubqkTe2K0701EYY_Ehia4fzFMOIzOo8exCguUA/edit#gid=877366376) - -- **The `font-size` and `line-height` tokens have been updated to a new scale. You may need to update your custom component styles to match the new scale:** - - [font-size scale](https://www.figma.com/file/5BZsOpNjbUHqgVh850yPBW/%5BResearch%5D-Typography-%26-Spacing-Refresh?type=design&node-id=244%3A188858&mode=design&t=vpFlyrSzO1jdpAPu-1) - - [line-height scale](https://www.figma.com/file/5BZsOpNjbUHqgVh850yPBW/%5BResearch%5D-Typography-%26-Spacing-Refresh?type=design&node-id=244%3A188858&mode=design&t=vpFlyrSzO1jdpAPu-1) - -### ActionList - -- **The `surfaceLevel` prop has been removed without replacement.** - - ```diff - - - + - ``` - -### Amount - -- **Amount component is now internationalized via [@razorpay/i18nify-js](https://www.npmjs.com/package/@razorpay/i18nify-js).** - -1. The `` component will now automatically format numbers based on the user's browser locale. For example, `` will render `₹1,23,456.79` for browsers with the `en-IN` default locale, whereas it will render `₹123,456.79` for browsers with the `en-US` locale. - -2. If you want to enable users to change the locale of your page, add the `@razorpay/i18nify-react` package and wrap your app inside the `I18nProvider`. Utilize the `setI18nState` utility to modify the locale. For more details, please refer to the [documentation](https://www.npmjs.com/package/@razorpay/i18nify-react). - -3. Additionally, if you prefer to maintain a fixed locale for your page and amount component, enclose your app within `..`. For more details, please refer to the [documentation](https://www.npmjs.com/package/@razorpay/i18nify-react). - -Example with `@razorpay/i18nify-react` - -```jsx -import React, { useEffect } from 'react'; -import { I18nProvider, useI18nContext } from '@razorpay/i18nify-react'; -import { BladeProvider, Amount } from '@razorpay/blade/components'; - -const ToggleAmount = ({ value }) => { - const { setI18nState } = useI18nContext(); - - function onLocaleChange() { - setI18nState({ locale: 'de-DE' }); - } - - return ( - <> - - - - ); -}; - -const App = () => { - return ( - - - - - - ); -}; -``` - -- **The accepted values for the `size` prop has been updated.** - - ```diff - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - - - - + - ``` - -- **The `intent` prop has been removed in favor of the `color` prop.** - - ```diff - - - + - - - - + - - - - + - - - - + - ``` - -- **The `prefix` prop has been removed in favor of the new `currencyIndicator` prop.** - - ```diff - - - + - - - - + - ``` - -### Alert - -- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** - - ```diff - - - + - - - - + - ``` - -- **The `intent` prop has been removed in favor of the `color` prop.** - - ```diff - - - + - ``` - -### Typography Components - -- **The `Title` component has been removed in favor of the `Heading` component.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `type` prop has been removed from the `Text`, `Heading`, & `Display` components in favor of the `color` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - - - Hello - + Hello - - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `contrast` prop has been removed from the `Text`, `Heading`, & `Display` components.** - - ```diff - - Hello - + Hello - - // contrast="high" doesn't exist anymore, so you will need to manually update these cases a new color token that matches the contrast you need. - - Hello - + Hello - ``` - -- **The `variant` prop has been removed from the `Heading` component.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `size` prop scale has been updated in the `Heading` component** - - ```diff - - Hello - + Hello - - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `caption` variant of the `Text` component now accepts only `size="small"`.** - - ```diff - - Hello - + Hello - ``` - -- **The `weight` prop now accepts `"semibold"` instead of `"bold"` in the ` Text`, `Heading` , & `Display` components. The `Code` component continues to accept `"bold"`.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - - - Hello - + Hello - ``` - -### Badge - -- **The `fontWeight` prop has been removed.** - - ```diff - - Hello - + Hello - ``` - -- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `variant` prop has been removed in favor of the `color` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** - - ```diff - - Hello - + Hello - ``` - -### Button & Link - -- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** - - ```diff - - - + - - - Hello - + Hello - ``` - -### Card - -- **The `surfaceLevel` prop has been removed in favor of the new `backgroundColor` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - ``` - -- The `CardHeaderBadge`, `CardHeaderCounter`, `CardHeaderAmount`, `CardHeaderText`, `CardHeaderLink`, and `CardHeaderIconButton` components have the same changes as `Badge`, `Counter`, `Amount`, `Text`, `Link`, and `Button` components respectively. - -### Chip & ChipGroup - -- **The `intent` prop has been removed in favor of the `color` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - - - - + - - - - + - ``` - -### Counter - -- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `variant` prop has been removed in favor of the `color` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - ``` - -- **The `intent` prop has been removed in favor of the `color` prop.** - - ```diff - - Hello - + Hello - ``` - -- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** - - ```diff - - Hello - + Hello - ``` - -### Divider - -- **The `"normal"` value for the `variant` prop has been updated with new styles, if you need the old styles, use the `"muted"` value.** - - ```diff - - Hello - + Hello - ``` - -### Dropdown - -- **The `onDismiss` prop has been removed in favor of the `onOpenChange` prop.** - - ```diff - - console.log("Dismissed!!!)}> Hello - + { - + if (!isOpen) { - + console.log("Dismissed!!!"); - + } - + }} - + > - - - Hello - + { - + if (!isOpen) { - + handleDropdownDismiss(); - + } - + }} - + > - ``` - -### Indicator - -- **The `intent` prop has been removed in favor of the `color` prop.** - - ```diff - - - + - - - Hello - + Hello - ``` - -### IconButton - -- **The `contrast` prop has been removed in favor of the new `emphasis` prop.** - - ```diff - - Hello - + Hello - - - Hello - + Hello - ``` - -### ProgressBar - -- **The `contrast` prop has been removed without replacement.** - - ```diff - - - + - ``` - -- **The `intent` prop has been removed in favor of the `color` prop.** - - ```diff - - - + - ``` - -### Skeleton - -- **The `contrast` prop has been removed without replacement.** - - ```diff - - - + - ``` - -### Spinner - -- **The `contrast` prop has been removed in favor of the `color` prop.** - - ```diff - - - + - - - - + - ``` - -- **The `"default"` value for the `color` prop has been removed in favor of the new `"primary"` value.** - - ```diff - - - + - ``` - -### Table - -- **The `surfaceLevel` prop has been removed without replacement.** - - ```diff - -
{`theme.motion.easing.exit.${key}`}{value} - +
- +
- ``` +This doc has been moved to [docs/migration-docs/upgrade-v11.md](./docs/migration-docs/upgrade-v11.md)