Skip to content

Commit b22a3ac

Browse files
committed
feat(styles): add conic gradient parsing
1 parent 5a29c68 commit b22a3ac

15 files changed

Lines changed: 1006 additions & 61 deletions

‎packages/react-native/Libraries/Components/View/__tests__/View-itest.js‎

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,24 +352,55 @@ describe('<View>', () => {
352352
],
353353
}}
354354
/>
355+
<View
356+
style={{
357+
backgroundImage:
358+
'conic-gradient(from 45deg, #e66465, #9198e5)',
359+
}}
360+
/>
361+
<View
362+
style={{
363+
backgroundImage: [
364+
{
365+
type: 'conic-gradient',
366+
from: '45deg',
367+
colorStops: [{color: '#e66465'}, {color: '#9198e5'}],
368+
},
369+
],
370+
}}
371+
/>
355372
</>,
356373
);
357374
});
358375

359-
const expectedProps = {
376+
const expectedRadialProps = {
360377
backgroundImage:
361378
'[radial-gradient(ellipse farthest-corner at 50% 50% , rgba(230, 100, 101, 1), rgba(145, 152, 229, 1))]',
362379
};
380+
const expectedConicProps = {
381+
backgroundImage:
382+
'[conic-gradient(from 45deg at 50% 50% , rgba(230, 100, 101, 1), rgba(145, 152, 229, 1))]',
383+
};
363384

364385
expect(root.getRenderedOutput().toJSON()).toEqual([
365386
{
366387
children: [],
367-
props: expectedProps,
388+
props: expectedRadialProps,
389+
type: 'View',
390+
},
391+
{
392+
children: [],
393+
props: expectedRadialProps,
394+
type: 'View',
395+
},
396+
{
397+
children: [],
398+
props: expectedConicProps,
368399
type: 'View',
369400
},
370401
{
371402
children: [],
372-
props: expectedProps,
403+
props: expectedConicProps,
373404
type: 'View',
374405
},
375406
]);

‎packages/react-native/Libraries/Components/View/__tests__/View-nativeCSSParsing-itest.js‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,28 @@ describe('<View> native CSS parsing', () => {
7171
expect(backgroundImage).toContain('linear-gradient');
7272
expect(backgroundImage).toContain('rgba(230, 100, 101, 1)');
7373
});
74+
75+
it('parses a conic-gradient()', () => {
76+
const backgroundImage = mountedProp(
77+
{
78+
backgroundImage: 'conic-gradient(from 45deg, #e66465, #9198e5)',
79+
},
80+
'backgroundImage',
81+
);
82+
expect(backgroundImage).toBe(
83+
'[conic-gradient(from 45deg at 50% 50% , rgba(230, 100, 101, 1), rgba(145, 152, 229, 1))]',
84+
);
85+
});
86+
87+
it('parses a four-value conic-gradient position', () => {
88+
expect(
89+
mountedProp(
90+
{
91+
backgroundImage: 'conic-gradient(at top 20px left 10px, red, blue)',
92+
},
93+
'backgroundImage',
94+
),
95+
).toContain('at 10px 20px');
96+
});
7497
});
7598
});

‎packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,18 @@ type RadialGradientValue = {
769769
}>,
770770
};
771771

772-
export type BackgroundImageValue = LinearGradientValue | RadialGradientValue;
772+
type ConicGradientValue = {
773+
type: 'conic-gradient',
774+
from?: string,
775+
position?: RadialGradientPosition,
776+
colorStops: ReadonlyArray<{
777+
color: ____ColorValue_Internal,
778+
positions?: ReadonlyArray<string>,
779+
}>,
780+
};
781+
782+
export type BackgroundImageValue =
783+
LinearGradientValue | RadialGradientValue | ConicGradientValue;
773784

774785
export type BackgroundSizeValue = {
775786
x: string | number,

‎packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,97 @@ describe('processBackgroundImage', () => {
4242
]);
4343
});
4444

45+
it('should process a conic gradient string', () => {
46+
const result = processBackgroundImage(
47+
'conic-gradient(from 45deg at 25% 75%, red 0deg, blue .5turn, red 100%)',
48+
);
49+
50+
expect(result).toEqual([
51+
{
52+
type: 'conic-gradient',
53+
from: 45,
54+
position: {left: '25%', top: '75%'},
55+
colorStops: [
56+
{color: processColor('red'), position: '0%'},
57+
{color: processColor('blue'), position: '50%'},
58+
{color: processColor('red'), position: '100%'},
59+
],
60+
},
61+
]);
62+
});
63+
64+
it('should process a conic gradient with four-value position', () => {
65+
expect(
66+
processBackgroundImage(
67+
'conic-gradient(at top 20px left 10px, red, blue)',
68+
),
69+
).toEqual([
70+
{
71+
type: 'conic-gradient',
72+
from: 0,
73+
position: {top: 20, left: 10},
74+
colorStops: [
75+
{color: processColor('red'), position: null},
76+
{color: processColor('blue'), position: null},
77+
],
78+
},
79+
]);
80+
});
81+
82+
it('should process conic gradient object syntax', () => {
83+
const input: ReadonlyArray<BackgroundImageValue> = [
84+
{
85+
type: 'conic-gradient',
86+
from: '0.25turn',
87+
position: {top: '50%', left: '50%'},
88+
colorStops: [
89+
{color: 'red', positions: ['0deg', '90deg']},
90+
{color: 'blue', positions: ['90deg', '360deg']},
91+
],
92+
},
93+
];
94+
95+
expect(processBackgroundImage(input)).toEqual([
96+
{
97+
type: 'conic-gradient',
98+
from: 90,
99+
position: {top: '50%', left: '50%'},
100+
colorStops: [
101+
{color: processColor('red'), position: '0%'},
102+
{color: processColor('red'), position: '25%'},
103+
{color: processColor('blue'), position: '25%'},
104+
{color: processColor('blue'), position: '100%'},
105+
],
106+
},
107+
]);
108+
});
109+
110+
it('should reject invalid conic gradient object stops', () => {
111+
expect(
112+
processBackgroundImage([
113+
{
114+
type: 'conic-gradient',
115+
colorStops: [{color: 'red'}],
116+
},
117+
]),
118+
).toEqual([]);
119+
120+
const numericStop = [
121+
{
122+
type: 'conic-gradient',
123+
colorStops: [{color: 'red', positions: [50]}, {color: 'blue'}],
124+
},
125+
];
126+
// $FlowFixMe[incompatible-type] - verifies runtime validation.
127+
expect(processBackgroundImage(numericStop)).toEqual([]);
128+
});
129+
130+
it('should reject conic gradient length color stops', () => {
131+
expect(processBackgroundImage('conic-gradient(red 10px, blue)')).toEqual(
132+
[],
133+
);
134+
});
135+
45136
it('should process a diagonal linear gradient', () => {
46137
const input = 'linear-gradient(to bottom right, red, blue)';
47138
const result = processBackgroundImage(input);

‎packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundPosition-itest.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,12 @@ describe('processBackgroundPosition', () => {
287287
]);
288288
});
289289

290+
it('should parse vertical position before horizontal position', () => {
291+
expect(processBackgroundPosition('top 75% left 25%')).toEqual([
292+
{top: '75%', left: '25%'},
293+
]);
294+
});
295+
290296
// Test multiple background positions (comma-separated)
291297
it('should parse multiple background positions', () => {
292298
expect(processBackgroundPosition('left top, right bottom')).toEqual([

0 commit comments

Comments
 (0)