mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
221d1eceda
Summary: - Current implementation does not follow CSS spec for rectangle boxes with corner angles. It is using non spec compliant algorithm to calculate start and end points. This PR follows the spec compliant algorithm to implement and makes sure Web, iOS and Android gradients are identical with corner angles. - Also, currently it is using `CAGradientLayer` which does not support spec compliant start and end points i.e. start and end point can be outside of rectangle bounds. This leads to inconsistent gradients on iOS for corner angles compared to web and android. So this PR replaces it with `CGGradient`. - I have also moved some files to make it easier to add more background image types in future. ## Changelog: [GENERAL] [FIXED] - Linear gradient start and end point algorithm. <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: https://github.com/facebook/react-native/pull/47003 Test Plan: - Added multiple gradient example which should be identical in all platforms (Web, iOS and Android) and tested thoroughly on all platforms. I think some visual test cases can help here. - I have referred to [blink's](https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/core/css/css_gradient_value.cc) implementation. ## Aside Took a while to understand the [spec](https://www.w3.org/TR/css-images-3/#corner-gradient-example), but felt great after getting it. Gradients should be 100% identical on all platforms now. Sorry i missed testing cornered angles + rectangles earlier and I found out it is inconsistent on platforms just this weekend 😅 <img width="1389" alt="Screenshot 2024-10-14 at 12 24 45 AM" src="https://github.com/user-attachments/assets/2f61eb87-502b-4b8c-88f3-d8a3cca9a7a3"> Reviewed By: joevilches Differential Revision: D64497127 Pulled By: jorge-cab fbshipit-source-id: 2647176ae2ee74b6c71f9061465b07dccdabcfc1
228 lines
5.8 KiB
JavaScript
228 lines
5.8 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
* @flow
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
|
|
|
import RNTesterText from '../../components/RNTesterText';
|
|
import React from 'react';
|
|
import {Platform, PlatformColor, StyleSheet, View} from 'react-native';
|
|
|
|
type Props = $ReadOnly<{
|
|
style: ViewStyleProp,
|
|
testID?: string,
|
|
}>;
|
|
|
|
function GradientBox(props: Props): React.Node {
|
|
return (
|
|
<View style={[styles.box, props.style]} testID={props.testID}>
|
|
<RNTesterText style={styles.text}>Linear Gradient</RNTesterText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
box: {
|
|
width: 200,
|
|
height: 100,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
marginVertical: 10,
|
|
},
|
|
text: {
|
|
color: 'white',
|
|
fontWeight: 'bold',
|
|
},
|
|
});
|
|
|
|
exports.title = 'Linear Gradient';
|
|
exports.category = 'UI';
|
|
exports.description = 'Examples of linear gradients applied to views.';
|
|
exports.examples = [
|
|
{
|
|
title: 'Basic Linear Gradient',
|
|
description: 'Linear gradient from top to bottom',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
style={{
|
|
experimental_backgroundImage: 'linear-gradient(#e66465, #9198e5);',
|
|
}}
|
|
testID="linear-gradient-basic"
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Linear Gradient with corner angle',
|
|
description: 'Rectangular Linear gradient with corner angle',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
style={{
|
|
experimental_backgroundImage: 'linear-gradient(45deg, red, blue);',
|
|
height: 300,
|
|
width: 140,
|
|
}}
|
|
testID="linear-gradient-rectangular-with-corner-angle"
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Multiple linear gradients',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-multiple"
|
|
style={{
|
|
experimental_backgroundImage: `
|
|
linear-gradient(0deg, white, rgba(238, 64, 53, 0.8), rgba(238, 64, 53, 0) 70%),
|
|
linear-gradient(45deg, white, rgba(243, 119, 54, 0.8), rgba(243, 119, 54, 0) 70%),
|
|
linear-gradient(90deg, white, rgba(253, 244, 152, 0.8), rgba(253, 244, 152, 0) 70%),
|
|
linear-gradient(135deg, white, rgba(123, 192, 67, 0.8), rgba(123, 192, 67, 0) 70%),
|
|
linear-gradient(180deg, white, rgba(3, 146, 207, 0.8), rgba(3, 146, 207, 0) 70%);
|
|
|
|
`,
|
|
borderRadius: 16,
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Diagonal Gradient',
|
|
description: 'Linear gradient from top-left to bottom-right',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-diagonal"
|
|
style={{
|
|
experimental_backgroundImage:
|
|
'linear-gradient(to bottom right, yellow, green)',
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Gradient with angle',
|
|
description: 'Linear gradient with angle',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-angle"
|
|
style={{
|
|
experimental_backgroundImage:
|
|
'linear-gradient(135deg, gray, brown)',
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Multiple Color Stops',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-color-stops"
|
|
style={{
|
|
experimental_backgroundImage:
|
|
'linear-gradient(to right, red 0%, yellow 30%, green 60%, blue 100%)',
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Linear gradient with object style syntax',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-object-style-syntax"
|
|
style={{
|
|
experimental_backgroundImage: [
|
|
{
|
|
type: 'linearGradient',
|
|
direction: 'to bottom',
|
|
colorStops: [
|
|
{color: 'purple', positions: ['0%']},
|
|
{color: 'orange', positions: ['100%']},
|
|
],
|
|
},
|
|
],
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Gradient with uniform border style',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-with-uniform-borders"
|
|
style={{
|
|
experimental_backgroundImage:
|
|
'linear-gradient(to bottom right, yellow, green);',
|
|
borderRadius: 16,
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Gradient with non-uniform border style',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-with-non-uniform-borders"
|
|
style={{
|
|
experimental_backgroundImage:
|
|
'linear-gradient(to bottom right, yellow, green);',
|
|
borderTopRightRadius: 8,
|
|
borderTopLeftRadius: 80,
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'Gradient with Platform colors',
|
|
render(): React.Node {
|
|
return (
|
|
<GradientBox
|
|
testID="linear-gradient-with-non-uniform-borders"
|
|
style={{
|
|
experimental_backgroundImage: [
|
|
{
|
|
type: 'linearGradient',
|
|
direction: 'to bottom',
|
|
colorStops: [
|
|
{
|
|
color: Platform.select({
|
|
ios: PlatformColor('systemTealColor'),
|
|
android: PlatformColor('@android:color/holo_purple'),
|
|
default: 'blue',
|
|
}),
|
|
positions: ['0%'],
|
|
},
|
|
{color: 'green', positions: ['100%']},
|
|
],
|
|
},
|
|
],
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
];
|