-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
7,609 additions
and
1,414 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--- | ||
"@razorpay/blade": minor | ||
--- | ||
|
||
feat: add `circular` variant for the `ProgressBar` component | ||
|
||
#### Changes | ||
|
||
- The `"meter"` & `"progress"` values for the `variant` prop are deprecated in favor of the new `type?: "meter" | "progress"` prop. | ||
- The `variant` prop now accepts `"linear"` & `"circular"` values. | ||
- **Usage:** | ||
|
||
```js | ||
<ProgressBar variant="circular" value={20}> label="Label" /> | ||
``` | ||
|
||
#### Migration with Codemod | ||
|
||
- The codemod will automatically update the `ProgressBar` component. 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/codemods/migrate-progressbar/transformers/index.ts --ignore-pattern="**/node_modules/**" | ||
``` | ||
|
||
- There might be some situations where the codemod falls short, If you encounter errors, refer the following examples to migrate the component manually: | ||
|
||
```diff | ||
- <ProgressBar value={20}> label="Label" /> | ||
+ <ProgressBar type="progress" value={20}> label="Label" /> | ||
|
||
- <ProgressBar variant="progress" value={20}> label="Label" /> | ||
+ <ProgressBar type="progress" variant="linear" value={20}> label="Label" /> | ||
|
||
- <ProgressBar variant="meter" value={20}> label="Label" /> | ||
+ <ProgressBar type="meter" variant="linear" value={20}> label="Label" /> | ||
``` | ||
|
||
|
||
|
28 changes: 28 additions & 0 deletions
28
...ages/blade/codemods/migrate-progressbar/transformers/__test__/migrate-progressbar.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { applyTransform } from '@hypermod/utils'; | ||
import * as transformer from '..'; | ||
|
||
it('should migrate the ProgressBar component', async () => { | ||
const result = await applyTransform( | ||
transformer, | ||
` | ||
const App = () => ( | ||
<> | ||
<ProgressBar value={20} label="Label" /> | ||
<ProgressBar variant="meter" value={20} label="Label" /> | ||
<ProgressBar variant="progress" value={20} label="Label" /> | ||
</> | ||
); | ||
`, | ||
{ parser: 'tsx' }, | ||
); | ||
|
||
expect(result).toMatchInlineSnapshot(` | ||
"const App = () => ( | ||
<> | ||
<ProgressBar value={20} label="Label" type="progress" /> | ||
<ProgressBar type="meter" value={20} label="Label" /> | ||
<ProgressBar type="progress" value={20} label="Label" /> | ||
</> | ||
);" | ||
`); | ||
}); |
89 changes: 89 additions & 0 deletions
89
packages/blade/codemods/migrate-progressbar/transformers/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import type { Transform } from 'jscodeshift'; | ||
|
||
import { red, isExpression } from '../../brand-refresh/transformers/utils'; | ||
|
||
const transformer: Transform = (file, api, options) => { | ||
// Don't transform if the file doesn't import `@razorapy/blade/components` because it's not using Blade components | ||
// Allow the migration test file to be transformed | ||
if (!file.source.includes('@razorpay/blade/components') && file.path !== undefined) { | ||
return file.source; | ||
} | ||
|
||
const j = api.jscodeshift; | ||
const root = j.withParser('tsx')(file.source); | ||
|
||
// Add type prop if variant prop is not defined | ||
try { | ||
root | ||
.find(j.JSXElement, { | ||
openingElement: { | ||
name: { | ||
name: 'ProgressBar', | ||
}, | ||
}, | ||
}) | ||
.replaceWith(({ node }) => { | ||
const variantAttribute = node.openingElement.attributes.find( | ||
(attribute) => attribute.name?.name === 'variant', | ||
); | ||
|
||
if (!variantAttribute) { | ||
node.openingElement.attributes?.push( | ||
j.jsxAttribute(j.jsxIdentifier('type'), j.literal('progress')), | ||
); | ||
} | ||
|
||
return node; | ||
}); | ||
} catch (error) { | ||
console.error( | ||
red( | ||
`⛔️ ${file.path}: Oops! Ran into an issue while adding the "type" prop to the ProgressBar component.`, | ||
), | ||
`\n${red(error.stack)}\n`, | ||
); | ||
} | ||
|
||
// Update the variant prop to type prop if defined | ||
try { | ||
root | ||
.find(j.JSXElement, { | ||
openingElement: { | ||
name: { | ||
name: 'ProgressBar', | ||
}, | ||
}, | ||
}) | ||
.find(j.JSXAttribute, { | ||
name: { | ||
name: 'variant', | ||
}, | ||
}) | ||
.replaceWith(({ node }) => { | ||
if (isExpression(node)) { | ||
console.warn( | ||
red('\n⛔️ Expression found in the "variant" attribute, please update manually:'), | ||
red(`${file.path}:${node.loc?.start.line}:${node.loc.start.column}\n`), | ||
); | ||
return node; | ||
} | ||
|
||
if (node.value?.value === 'progress' || node.value?.value === 'meter') { | ||
node.name.name = 'type'; | ||
} | ||
|
||
return node; | ||
}); | ||
} catch (error) { | ||
console.error( | ||
red( | ||
`⛔️ ${file.path}: Oops! Ran into an issue while updating the "variant" prop of the ProgressBar component.`, | ||
), | ||
`\n${red(error.stack)}\n`, | ||
); | ||
} | ||
|
||
return root.toSource(options.printOptions); | ||
}; | ||
|
||
export default transformer; |
167 changes: 167 additions & 0 deletions
167
packages/blade/src/components/ProgressBar/CircularProgressBar.native.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import React, { useEffect } from 'react'; | ||
import styled from 'styled-components/native'; | ||
import Animated, { | ||
cancelAnimation, | ||
useAnimatedStyle, | ||
useSharedValue, | ||
withDelay, | ||
withRepeat, | ||
withSequence, | ||
withTiming, | ||
} from 'react-native-reanimated'; | ||
import { Text as SVGText, Circle } from 'react-native-svg'; | ||
import type { CircularProgressBarFilledProps } from './types'; | ||
import { circularProgressSizeTokens, getCircularProgressSVGTokens } from './progressBarTokens'; | ||
import { CircularProgressLabel } from './CircularProgressLabel'; | ||
import getIn from '~utils/lodashButBetter/get'; | ||
import BaseBox from '~components/Box/BaseBox'; | ||
import { makeMotionTime } from '~utils/makeMotionTime'; | ||
import type { TextProps } from '~components/Typography'; | ||
import { getTextProps } from '~components/Typography'; | ||
import { useTheme } from '~components/BladeProvider'; | ||
import { castNativeType } from '~utils'; | ||
import { Svg } from '~components/Icons/_Svg'; | ||
import getBaseTextStyles from '~components/Typography/BaseText/getBaseTextStyles'; | ||
|
||
const pulseAnimation = { | ||
opacityInitial: 1, | ||
opacityMid: 0.65, | ||
opacityFinal: 1, | ||
}; | ||
|
||
const StyledSVGText = styled(SVGText)<Pick<TextProps<{ variant: 'body' }>, 'size' | 'weight'>>( | ||
({ theme, size, weight }) => { | ||
const textProps = getTextProps({ variant: 'body', size, weight }); | ||
|
||
return { | ||
...getBaseTextStyles({ theme, ...textProps }), | ||
strokeWidth: 0, | ||
fill: getIn(theme.colors, textProps.color!), | ||
}; | ||
}, | ||
); | ||
|
||
const CircularProgressBarFilled = ({ | ||
progressPercent, | ||
fillColor, | ||
backgroundColor, | ||
size = 'small', | ||
label, | ||
showPercentage = true, | ||
isMeter, | ||
motionEasing, | ||
pulseMotionDuration, | ||
pulseMotionDelay, | ||
fillMotionDuration, | ||
}: CircularProgressBarFilledProps): React.ReactElement => { | ||
const { | ||
sqSize, | ||
strokeWidth, | ||
radius, | ||
viewBox, | ||
dashArray, | ||
dashOffset, | ||
} = getCircularProgressSVGTokens({ size, progressPercent }); | ||
|
||
const AnimatedCircle = Animated.createAnimatedComponent(Circle); | ||
const animatedOpacity = useSharedValue(pulseAnimation.opacityInitial); | ||
const animatedStrokeDashoffset = useSharedValue(dashOffset); | ||
const { theme } = useTheme(); | ||
const fillAndPulseEasing = getIn(theme.motion, motionEasing); | ||
const pulseDuration = | ||
castNativeType(makeMotionTime(getIn(theme.motion, pulseMotionDuration))) / 2; | ||
|
||
// Trigger animation for progress fill | ||
useEffect(() => { | ||
const fillDuration = castNativeType(makeMotionTime(getIn(theme.motion, fillMotionDuration))); | ||
animatedStrokeDashoffset.value = withTiming(dashOffset, { | ||
duration: fillDuration, | ||
easing: fillAndPulseEasing, | ||
}); | ||
return () => { | ||
cancelAnimation(animatedStrokeDashoffset); | ||
}; | ||
}, [dashOffset, animatedStrokeDashoffset, fillMotionDuration, theme, fillAndPulseEasing]); | ||
|
||
// Trigger pulsating animation | ||
useEffect(() => { | ||
const pulsatingAnimationTimingConfig = { | ||
duration: pulseDuration, | ||
easing: fillAndPulseEasing, | ||
}; | ||
if (!isMeter) { | ||
animatedOpacity.value = withDelay( | ||
castNativeType(makeMotionTime(getIn(theme.motion, pulseMotionDelay))), | ||
withRepeat( | ||
withSequence( | ||
withTiming(pulseAnimation.opacityMid, pulsatingAnimationTimingConfig), | ||
withTiming(pulseAnimation.opacityFinal, pulsatingAnimationTimingConfig), | ||
), | ||
-1, | ||
), | ||
); | ||
} | ||
|
||
return () => { | ||
cancelAnimation(animatedOpacity); | ||
}; | ||
}, [animatedOpacity, fillAndPulseEasing, pulseDuration, pulseMotionDelay, theme, isMeter]); | ||
|
||
const firstIndicatorStyles = useAnimatedStyle(() => { | ||
return { | ||
strokeDashoffset: animatedStrokeDashoffset.value, | ||
opacity: progressPercent < 100 ? animatedOpacity.value : 1, | ||
}; | ||
}); | ||
|
||
return ( | ||
<BaseBox display="flex" width="fit-content" alignItems="center"> | ||
<Svg width={String(sqSize)} height={String(sqSize)} viewBox={viewBox}> | ||
<Circle | ||
fill="none" | ||
stroke={backgroundColor} | ||
cx={String(sqSize / 2)} | ||
cy={String(sqSize / 2)} | ||
r={String(radius)} | ||
strokeWidth={`${strokeWidth}px`} | ||
/> | ||
|
||
<AnimatedCircle | ||
fill="none" | ||
stroke={fillColor} | ||
cx={sqSize / 2} | ||
cy={sqSize / 2} | ||
r={radius} | ||
strokeWidth={`${strokeWidth}px`} | ||
// Start progress marker at 12 O'Clock | ||
transform={`rotate(-90 ${sqSize / 2} ${sqSize / 2})`} | ||
strokeDasharray={dashArray} | ||
strokeDashoffset={dashOffset} | ||
style={firstIndicatorStyles} | ||
/> | ||
|
||
{showPercentage && size !== 'small' && ( | ||
<StyledSVGText | ||
size={circularProgressSizeTokens[size].percentTextSize} | ||
weight="semibold" | ||
x="50%" | ||
y="50%" | ||
textAnchor="middle" | ||
dy=".5em" | ||
> | ||
{`${progressPercent}%`} | ||
</StyledSVGText> | ||
)} | ||
</Svg> | ||
|
||
<CircularProgressLabel | ||
progressPercent={progressPercent} | ||
size={size} | ||
label={label} | ||
showPercentage={showPercentage} | ||
/> | ||
</BaseBox> | ||
); | ||
}; | ||
|
||
export { CircularProgressBarFilled }; |
Oops, something went wrong.