Skip to content

Commit 0b30f47

Browse files
M-i-k-e-ladids1221
authored andcommitted
Incubator.Gradient - new component (#3795)
* Incubator.Gradient - new component * Oops * Revert LinearGradientPackage change * Improve screen
1 parent a71c5d2 commit 0b30f47

File tree

14 files changed

+451
-16
lines changed

14 files changed

+451
-16
lines changed

demo/src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ module.exports = {
263263
get IncubatorCalendarScreen() {
264264
return require('./screens/incubatorScreens/IncubatorCalendarScreen').default;
265265
},
266+
get IncubatorGradient() {
267+
return require('./screens/incubatorScreens/IncubatorGradientScreen').default;
268+
},
266269
// realExamples
267270
get AppleMusic() {
268271
return require('./screens/realExamples/AppleMusic').default;

demo/src/screens/ExampleScreenPresenter.tsx

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,29 @@ import {
1616
View
1717
} from 'react-native-ui-lib';
1818

19-
interface RadioGroupOptions {
19+
interface StateOptions {
20+
state?: string;
21+
setState?: React.Dispatch<React.SetStateAction<any /** no suitable solution for enum */>>;
22+
}
23+
24+
interface RadioGroupBaseOptions {
2025
isRow?: boolean;
21-
afterValueChanged?: () => void;
2226
useValueAsLabel?: boolean;
2327
}
2428

29+
type RadioGroupOptions =
30+
| (RadioGroupBaseOptions & {
31+
afterValueChanged?: () => void;
32+
})
33+
| (RadioGroupBaseOptions & StateOptions);
34+
2535
interface BooleanGroupOptions {
2636
spread?: boolean;
2737
afterValueChanged?: () => void;
2838
state?: boolean;
2939
setState?: React.Dispatch<React.SetStateAction<boolean>>;
3040
}
3141

32-
interface SegmentsExtraOptions {
33-
state?: string;
34-
setState?: React.Dispatch<React.SetStateAction<any /** no suitable solution for enum */>>;
35-
}
36-
3742
export function renderHeader(title: string, others?: TextProps) {
3843
return (
3944
<Text text30 $textDefault {...others}>
@@ -103,9 +108,10 @@ export function renderBooleanGroup(title: string, options: string[]) {
103108
export function renderRadioGroup(title: string,
104109
key: string,
105110
options: object,
106-
{isRow, afterValueChanged, useValueAsLabel}: RadioGroupOptions = {}) {
107111
// @ts-ignore
108-
const value = this.state[key];
112+
{isRow, afterValueChanged, useValueAsLabel, state, setState}: RadioGroupOptions = {}) {
113+
// @ts-ignore
114+
const value = state ?? this.state[key];
109115
return (
110116
<View marginB-s2>
111117
{!_.isUndefined(title) && (
@@ -118,7 +124,18 @@ export function renderRadioGroup(title: string,
118124
style={isRow && styles.rowWrap}
119125
initialValue={value}
120126
// @ts-ignore
121-
onValueChange={value => this.setState({[key]: value}, afterValueChanged)}
127+
onValueChange={value => {
128+
if (setState) {
129+
setState(value);
130+
if (afterValueChanged) {
131+
// eslint-disable-next-line no-restricted-syntax
132+
console.error('afterValueChanged is not supported together with the state option');
133+
}
134+
} else {
135+
// @ts-ignore
136+
this.setState({[key]: value}, afterValueChanged);
137+
}
138+
}}
122139
>
123140
{_.map(options, (value, key) => {
124141
return (
@@ -159,9 +176,25 @@ export function renderColorOption(title: string,
159176

160177
export function renderSliderOption(title: string,
161178
key: string,
162-
{min = 0, max = 10, step = 1, initial = 0, sliderText = ''}) {
179+
{
180+
min = 0,
181+
max = 10,
182+
step = 1,
183+
initial = 0,
184+
sliderText = '',
185+
state,
186+
setState
187+
}: {
188+
min?: number;
189+
max?: number;
190+
step?: number;
191+
initial?: number;
192+
sliderText?: string;
193+
state?: number;
194+
setState?: React.Dispatch<React.SetStateAction<number>>;
195+
}) {
163196
// @ts-ignore
164-
const value = this.state[key] || initial;
197+
const value = state ?? this.state[key] ?? initial;
165198
return (
166199
<View marginV-s2>
167200
<Text marginB-s1 text70M $textDefault>
@@ -177,7 +210,14 @@ export function renderSliderOption(title: string,
177210
maximumValue={max}
178211
step={step}
179212
// @ts-ignore
180-
onValueChange={value => this.setState({[key]: value})}
213+
onValueChange={value => {
214+
if (setState) {
215+
setState(value);
216+
} else {
217+
// @ts-ignore
218+
this.setState({[key]: value});
219+
}
220+
}}
181221
/>
182222
<Text marginL-s4 text70 $textDefault style={styles.text}>
183223
{sliderText}
@@ -191,7 +231,7 @@ export function renderSliderOption(title: string,
191231
export function renderMultipleSegmentOptions(title: string,
192232
key: string,
193233
options: (SegmentedControlItemProps & {value: any})[],
194-
{state, setState}: SegmentsExtraOptions = {}) {
234+
{state, setState}: StateOptions = {}) {
195235
// @ts-ignore
196236
const value = state ?? this.state[key];
197237
const index = _.findIndex(options, {value});

demo/src/screens/MenuStructure.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ export const navigationData = {
208208
tags: 'text field expandable input picker',
209209
screen: 'unicorn.components.IncubatorExpandableOverlayScreen'
210210
},
211-
{title: 'PanView', tags: 'pan swipe drag', screen: 'unicorn.incubator.PanViewScreen'}
211+
{title: 'PanView', tags: 'pan swipe drag', screen: 'unicorn.incubator.PanViewScreen'},
212+
{title: 'Gradient', tags: 'gradient', screen: 'unicorn.components.IncubatorGradientScreen'}
212213
]
213214
},
214215
Inspirations: {
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React, {useEffect, useMemo, useState} from 'react';
2+
import {Assets, View, Text, Incubator, Icon, Colors} from 'react-native-ui-lib';
3+
import {renderRadioGroup, renderSliderOption} from '../ExampleScreenPresenter';
4+
5+
const {Gradient} = Incubator;
6+
7+
const COLORS = [Colors.$backgroundPrimaryHeavy, Colors.$backgroundPrimaryHeavy, Colors.$backgroundPrimaryMedium];
8+
9+
const GradientScreen = () => {
10+
const [type, setType] = useState('rectangle');
11+
const [children, setChildren] = useState('none');
12+
const [alignment, setAlignment] = useState('none');
13+
const [size, setSize] = useState('fixed');
14+
const [error, setError] = useState('');
15+
const [angle, setAngle] = useState(0);
16+
17+
const gradientProps = useMemo(() => {
18+
switch (type) {
19+
case 'rectangle':
20+
return size === 'fixed' ? {type: 'rectangle', width: 100, height: 100} : {type: 'rectangle'};
21+
case 'circle':
22+
return size === 'fixed' ? {type: 'circle', radius: 50} : {type: 'circle'};
23+
case 'border':
24+
return size === 'fixed' ? {type: 'border', width: 100, height: 100} : {type: 'border'};
25+
}
26+
}, [type, size]);
27+
28+
const childrenProps = useMemo(() => {
29+
switch (children) {
30+
case 'shortText':
31+
return <Text>Lorem ipsum dolor sit amet.</Text>;
32+
case 'text':
33+
return (
34+
<Text>
35+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
36+
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
37+
ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
38+
nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
39+
anim id est laborum.
40+
</Text>
41+
);
42+
case 'icon':
43+
return <Icon source={Assets.icons.demo.search}/>;
44+
}
45+
}, [children]);
46+
47+
const alignmentProp = useMemo(() => {
48+
switch (alignment) {
49+
case 'none':
50+
return undefined;
51+
case 'center':
52+
return {center: true};
53+
case 'centerH':
54+
return {centerH: true};
55+
case 'centerV':
56+
return {centerV: true};
57+
}
58+
}, [alignment]);
59+
60+
useEffect(() => {
61+
if (children === 'none' && size === 'flex' && type !== 'border') {
62+
setError('No children + flex gives no gradient');
63+
} else if (size === 'flex' && type === 'circle') {
64+
setError('flex size will result with an ellipse instead of a circle');
65+
} else {
66+
setError('');
67+
}
68+
}, [children, size, type]);
69+
70+
return (
71+
<View padding-page>
72+
{renderRadioGroup('Select type',
73+
'type',
74+
{Rectangle: 'rectangle', Circle: 'circle', Border: 'border'},
75+
{isRow: true, state: type, setState: setType})}
76+
{renderRadioGroup('Select children',
77+
'children',
78+
{No: 'none', 'Short text': 'shortText', Text: 'text', Icon: 'icon'},
79+
{isRow: true, state: children, setState: setChildren})}
80+
{renderRadioGroup('Select children`s alignment',
81+
'alignment',
82+
{None: 'none', Center: 'center', CenterH: 'centerH', CenterV: 'centerV'},
83+
{isRow: true, state: alignment, setState: setAlignment})}
84+
{renderRadioGroup('Select size',
85+
'size',
86+
{Fixed: 'fixed', Flex: 'flex'},
87+
{isRow: true, state: size, setState: setSize})}
88+
<View marginH-s10>
89+
{renderSliderOption('Angle', 'angle', {
90+
min: 0,
91+
max: 360,
92+
step: 1,
93+
state: angle,
94+
setState: setAngle
95+
})}
96+
</View>
97+
<Gradient
98+
colors={COLORS}
99+
// @ts-expect-error
100+
type={type}
101+
{...gradientProps}
102+
{...alignmentProp}
103+
angle={angle}
104+
>
105+
{childrenProps}
106+
</Gradient>
107+
<Text marginT-s10 center $textDangerLight>
108+
{error}
109+
</Text>
110+
</View>
111+
);
112+
};
113+
114+
export default GradientScreen;

demo/src/screens/incubatorScreens/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export function registerScreens(registrar) {
66
registrar('unicorn.components.IncubatorToastScreen', () => require('./IncubatorToastScreen').default);
77
registrar('unicorn.incubator.PanViewScreen', () => require('./PanViewScreen').default);
88
registrar('unicorn.components.IncubatorSliderScreen', () => require('./IncubatorSliderScreen').default);
9+
registrar('unicorn.components.IncubatorGradientScreen', () => require('./IncubatorGradientScreen').default);
910
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
import {LinearGradientPackage} from 'optionalDeps';
3+
const LinearGradient = LinearGradientPackage?.default;
4+
import {BorderGradientProps} from './types';
5+
import View from '../../components/view';
6+
import Spacings from '../../style/spacings';
7+
import Colors from '../../style/colors';
8+
import useAngleTransform from './useAngleTransform';
9+
10+
const BorderGradient = (props: BorderGradientProps) => {
11+
const {colors, borderWidth = Spacings.s1, borderRadius, children, width, height, angle, ...others} = props;
12+
13+
const innerWidth = width ? width - borderWidth * 2 : undefined;
14+
const innerHeight = height ? height - borderWidth * 2 : undefined;
15+
const {start, end} = useAngleTransform({angle});
16+
17+
if (!LinearGradient) {
18+
return null;
19+
}
20+
21+
return (
22+
<View width={width} height={height}>
23+
<LinearGradient colors={colors} start={start} end={end} style={{borderRadius}}>
24+
<View
25+
bg-white
26+
width={innerWidth}
27+
height={innerHeight}
28+
style={{margin: borderWidth, borderRadius, borderWidth: 0, borderColor: Colors.transparent}}
29+
{...others}
30+
>
31+
{children}
32+
</View>
33+
</LinearGradient>
34+
</View>
35+
);
36+
};
37+
38+
export default BorderGradient;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import {LinearGradientPackage} from 'optionalDeps';
3+
const LinearGradient = LinearGradientPackage?.default;
4+
import {CircleGradientProps} from './types';
5+
import View from '../../components/view';
6+
import useAngleTransform from './useAngleTransform';
7+
8+
const CircleGradient = (props: CircleGradientProps) => {
9+
const {colors, radius, angle, children, ...others} = props;
10+
11+
const internalDiameter = radius ? radius * 2 : undefined;
12+
const {start, end} = useAngleTransform({angle});
13+
14+
if (!LinearGradient) {
15+
return null;
16+
}
17+
18+
return (
19+
<View width={internalDiameter} height={internalDiameter} style={{borderRadius: 999, overflow: 'hidden'}}>
20+
<LinearGradient colors={colors} start={start} end={end}>
21+
<View width={internalDiameter} height={internalDiameter} {...others}>
22+
{children}
23+
</View>
24+
</LinearGradient>
25+
</View>
26+
);
27+
};
28+
29+
export default CircleGradient;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
import {LinearGradientPackage} from 'optionalDeps';
3+
const LinearGradient = LinearGradientPackage?.default;
4+
import {RectangleGradientProps} from './types';
5+
import View from '../../components/view';
6+
import useAngleTransform from './useAngleTransform';
7+
8+
const RectangleGradient = (props: RectangleGradientProps) => {
9+
const {colors, width, height, angle, children, ...others} = props;
10+
11+
const {start, end} = useAngleTransform({angle});
12+
13+
if (!LinearGradient) {
14+
return null;
15+
}
16+
17+
return (
18+
<View width={width} height={height}>
19+
<LinearGradient colors={colors} start={start} end={end}>
20+
<View width={width} height={height} {...others}>
21+
{children}
22+
</View>
23+
</LinearGradient>
24+
</View>
25+
);
26+
};
27+
28+
export default RectangleGradient;

0 commit comments

Comments
 (0)