Skip to content

Commit e3782e9

Browse files
mendyEdriethanshar
andauthored
Feat/basic ui (#1038)
* Removed 'wheel` animation to match the design guidlines + added default colors and font * added text configuration props * on selecting item - wheel will scroll to the right row * added Fader and extract separators * Calculate current active index * added onChange event * Increase visible rows to 5 * change separator color to be visible * added demo code * update reanimated version * types changes * type update * extract logic to it's own type * refactor item center calculation to be based on the offset * update demo screen according to the new api * pass style to item * added unittests to listMiddleIndex calculations * made demo more clear * Update src/incubator/WheelPicker/index.tsx Co-authored-by: Ethan Sharabi <[email protected]> * PR fixes - naming * PR fixes - naming * PR fix - optional chaining Co-authored-by: Ethan Sharabi <[email protected]>
1 parent ab8f491 commit e3782e9

File tree

8 files changed

+205
-75
lines changed

8 files changed

+205
-75
lines changed

demo/src/screens/incubatorScreens/WheelPickerScreen.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import React from 'react';
2-
import {View, Text, Incubator} from 'react-native-ui-lib';
1+
import React, {useCallback} from 'react';
2+
import {View, Text, Incubator, Colors, Typography} from 'react-native-ui-lib';
33
import _ from 'lodash';
4+
import {ItemProps} from 'src/incubator/WheelPicker/Item';
45

56
// Months
67
const months = [
@@ -22,14 +23,26 @@ const months = [
2223
const years = _.times(2020, i => i);
2324

2425
export default () => {
26+
27+
const onChange = useCallback((index: number, item?: ItemProps) => {
28+
console.log(item, index);
29+
}, []);
30+
2531
return (
2632
<View flex padding-page>
2733
<Text h1>Wheel Picker</Text>
28-
<View flex centerH paddingT-page>
34+
<View flex centerV centerH paddingT-page>
2935
<Text h3>Months</Text>
30-
<Incubator.WheelPicker items={_.map(months, i => ({text: i, value: i}))} />
36+
<Incubator.WheelPicker
37+
onChange={onChange}
38+
activeTextColor={Colors.primary}
39+
inactiveTextColor={Colors.grey20}
40+
items={_.map(months, i => ({text: i, value: i}))}
41+
textStyle={{...Typography.text60R}}
42+
/>
43+
3144
<Text h3 marginT-s5>Years</Text>
32-
<Incubator.WheelPicker items={_.map(years, i => ({text: i, value: i}))} />
45+
<Incubator.WheelPicker onChange={onChange} items={_.map(years, i => ({text: '' + i, value: i}))} />
3346
</View>
3447
</View>
3548
);

generatedTypes/incubator/WheelPicker/Item.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ interface InternalProps extends ItemProps {
88
index: number;
99
offset: any;
1010
itemHeight: number;
11-
textStyle?: TextStyle;
11+
activeColor?: string;
12+
inactiveColor?: string;
13+
style?: TextStyle;
1214
onSelect: (index: number) => void;
1315
}
14-
declare const _default: ({ index, text, textStyle, itemHeight, onSelect, offset }: InternalProps) => JSX.Element;
16+
declare const _default: ({ index, text, itemHeight, onSelect, offset, activeColor, inactiveColor, style }: InternalProps) => JSX.Element;
1517
export default _default;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare type ItemType = {
2+
itemHeight: number;
3+
listSize: number;
4+
};
5+
declare const _default: ({ itemHeight, listSize }: ItemType) => (offset: number) => number;
6+
export default _default;

generatedTypes/incubator/WheelPicker/index.d.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,30 @@
22
import { TextStyle } from 'react-native';
33
import { ItemProps } from './Item';
44
export interface WheelPickerProps {
5+
/**
6+
* Data source for WheelPicker
7+
*/
58
items?: ItemProps[];
9+
/**
10+
* Describe the height of each item in the WheelPicker
11+
*/
612
itemHeight?: number;
7-
itemTextStyle?: TextStyle;
8-
onChange: (item: ItemProps, index: number) => void;
13+
/**
14+
* Text color for the focused row
15+
*/
16+
activeTextColor?: string;
17+
/**
18+
* Text color for other, non-focused rows
19+
*/
20+
inactiveTextColor?: string;
21+
/**
22+
* Row text style
23+
*/
24+
textStyle?: TextStyle;
25+
/**
26+
* Event, on active row change
27+
*/
28+
onChange: (index: number, item?: ItemProps) => void;
929
}
10-
declare const WheelPicker: ({ items, itemHeight, itemTextStyle }: WheelPickerProps) => JSX.Element;
30+
declare const WheelPicker: ({ items, itemHeight, activeTextColor, inactiveTextColor, textStyle, onChange: onChangeEvent }: WheelPickerProps) => JSX.Element;
1131
export default WheelPicker;

src/incubator/WheelPicker/Item.tsx

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, {useCallback, useMemo} from 'react';
2-
import Animated, {concat} from 'react-native-reanimated';
3-
import {transformOrigin} from 'react-native-redash';
2+
import Animated, {interpolateColors} from 'react-native-reanimated';
43
import Text from '../../components/text';
54
import TouchableOpacity from '../../components/touchableOpacity';
65
import {TextStyle} from 'react-native';
6+
import {Colors} from '../../../src/style';
77

88
const AnimatedTouchableOpacity = Animated.createAnimatedComponent(
99
TouchableOpacity
@@ -19,67 +19,49 @@ interface InternalProps extends ItemProps {
1919
index: number;
2020
offset: any;
2121
itemHeight: number;
22-
textStyle?: TextStyle;
22+
activeColor?: string;
23+
inactiveColor?: string;
24+
style?: TextStyle;
2325
onSelect: (index: number) => void;
2426
}
2527

2628
export default ({
2729
index,
2830
text,
29-
textStyle,
30-
// value,
3131
itemHeight,
3232
onSelect,
33-
offset
33+
offset,
34+
activeColor = Colors.primary,
35+
inactiveColor = Colors.grey20,
36+
style
3437
}: InternalProps) => {
38+
39+
const selectItem = useCallback(() => onSelect(index), [index]);
3540
const itemOffset = index * itemHeight;
36-
const opacity = Animated.interpolate(offset, {
37-
inputRange: [itemOffset - itemHeight, itemOffset, itemOffset + itemHeight],
38-
outputRange: [0.5, 1, 0.5]
39-
});
4041

41-
const rotateXValue = useMemo(() => {
42-
return Animated.interpolate(offset, {
42+
const color = useMemo(() => {
43+
return interpolateColors(offset, {
4344
inputRange: [
44-
itemOffset - 2.5 * itemHeight,
45+
itemOffset - itemHeight,
4546
itemOffset,
46-
itemOffset + 2.5 * itemHeight
47+
itemOffset + itemHeight
4748
],
48-
outputRange: [-85, 0, -85]
49+
outputColorRange: [inactiveColor, activeColor, inactiveColor]
4950
});
5051
}, [itemHeight]);
51-
52-
const rotateX = concat(rotateXValue, 'deg');
53-
54-
const scale = useMemo(() => {
55-
return Animated.interpolate(offset, {
56-
inputRange: [
57-
itemOffset - 2 * itemHeight,
58-
itemOffset,
59-
itemOffset + 2 * itemHeight
60-
],
61-
// outputRange: [0.8, 1, 0.8], // with scale change
62-
outputRange: [1, 1, 1],
63-
});
64-
}, [itemHeight]);
65-
66-
const selectItem = useCallback(() => onSelect(index), [index]);
67-
52+
6853
return (
6954
<AnimatedTouchableOpacity
7055
activeOpacity={1}
7156
style={{
72-
height: itemHeight,
73-
opacity,
74-
// @ts-ignore
75-
transform: transformOrigin({x: 125, y: 24}, {rotateX})
57+
height: itemHeight
7658
}}
7759
key={index}
7860
center
7961
onPress={selectItem}
8062
index={index}
8163
>
82-
<AnimatedText text60 style={[textStyle, {transform: [{scale}]}]}>
64+
<AnimatedText text60R style={{color, ...style}}>
8365
{text}
8466
</AnimatedText>
8567
</AnimatedTouchableOpacity>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import useMiddleIndex from '../helpers/useListMiddleIndex';
2+
3+
describe('Finds list\'s middle index', () => {
4+
5+
it('When list is at offset 0, it should return the index of the first item', () => {
6+
const sut = useMiddleIndex({itemHeight: 50, listSize: 10});
7+
const offset = 0;
8+
expect(sut(offset)).toEqual(0);
9+
});
10+
11+
it('When list is at offset 100, it means we are at passed on 2 items', () => {
12+
const sut = useMiddleIndex({itemHeight: 50, listSize: 10});
13+
const offset = 100;
14+
expect(sut(offset)).toEqual(2);
15+
});
16+
17+
it('Make sure calculation changes on the middle of the item height', () => {
18+
const sut = useMiddleIndex({itemHeight: 50, listSize: 10});
19+
let offset = 24;
20+
expect(sut(offset)).toEqual(0);
21+
22+
offset = 26;
23+
expect(sut(offset)).toEqual(1);
24+
});
25+
26+
it('Make sure calculation does not exceeds the number of items', () => {
27+
const sut = useMiddleIndex({itemHeight: 50, listSize: 10});
28+
let offset = 501;
29+
expect(sut(offset)).toEqual(9);
30+
31+
offset = 600;
32+
expect(sut(offset)).toEqual(9);
33+
});
34+
35+
it('Make sure calculation does not less then 0', () => {
36+
const sut = useMiddleIndex({itemHeight: 50, listSize: 10});
37+
const offset = -100;
38+
expect(sut(offset)).toEqual(0);
39+
});
40+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
type ItemType = {
2+
itemHeight: number;
3+
listSize: number;
4+
}
5+
6+
export default ({itemHeight, listSize}: ItemType) => {
7+
const valueInRange = (value: number, min: number, max: number): number => {
8+
if (value < min || value === -0) {
9+
return min;
10+
}
11+
if (value > max) {
12+
return max;
13+
}
14+
return value;
15+
};
16+
17+
const middleIndex = (offset: number): number => {
18+
const calculatedIndex = Math.round(offset / itemHeight);
19+
return valueInRange(calculatedIndex, 0, listSize - 1);
20+
};
21+
22+
return middleIndex
23+
};

0 commit comments

Comments
 (0)