@utilitywarehouse/hearth-react-native 0.28.0 → 0.28.1-testid-fix-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -4
- package/.turbo/turbo-lint.log +70 -68
- package/CHANGELOG.md +6 -0
- package/build/components/DatePicker/TimePicker.d.ts +3 -0
- package/build/components/DatePicker/TimePicker.js +84 -0
- package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
- package/build/components/DatePicker/time-picker/animated-math.js +19 -0
- package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-native.js +17 -0
- package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-picker.js +10 -0
- package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-web.js +21 -0
- package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
- package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
- package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
- package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel-web.js +146 -0
- package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel.js +10 -0
- package/build/components/Select/Select.d.ts +1 -1
- package/build/components/Select/Select.js +4 -4
- package/build/components/Select/Select.props.d.ts +4 -0
- package/build/components/Select/SelectOption.d.ts +1 -1
- package/build/components/Select/SelectOption.js +2 -2
- package/build/components/VerificationInput/VerificationInput.js +3 -3
- package/docs/all-components.mdx +2 -2
- package/docs/changelog.mdx +178 -1
- package/docs/components/AllComponents.web.tsx +65 -125
- package/docs/components/NextPrevPage.tsx +28 -44
- package/docs/components/index.ts +1 -0
- package/docs/getting-started.mdx +3 -3
- package/docs/hooks.mdx +2 -2
- package/docs/introduction.mdx +1 -1
- package/docs/layout-components.docs.mdx +11 -2
- package/docs/styling.mdx +3 -3
- package/package.json +3 -3
- package/scripts/copyChangelog.js +8 -1
- package/src/components/Banner/Banner.docs.mdx +2 -1
- package/src/components/Center/Center.docs.mdx +6 -5
- package/src/components/Checkbox/Checkbox.docs.mdx +2 -1
- package/src/components/CurrencyInput/CurrencyInput.docs.mdx +2 -1
- package/src/components/HighlightBanner/HighlightBanner.docs.mdx +2 -1
- package/src/components/Input/Input.docs.mdx +2 -1
- package/src/components/Menu/Menu.docs.mdx +2 -1
- package/src/components/Modal/Modal.docs.mdx +2 -1
- package/src/components/Radio/Radio.docs.mdx +2 -1
- package/src/components/Select/Select.docs.mdx +2 -1
- package/src/components/Select/Select.props.ts +4 -0
- package/src/components/Select/Select.tsx +9 -2
- package/src/components/Select/SelectOption.tsx +2 -0
- package/src/components/Switch/Switch.docs.mdx +3 -2
- package/src/components/VerificationInput/VerificationInput.tsx +3 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Animated, Platform, View, } from 'react-native';
|
|
4
|
+
import WheelPickerItem from './wheel-picker-item';
|
|
5
|
+
import styles from './wheel-picker.style';
|
|
6
|
+
const WheelPicker = ({ value, options, onChange, selectedIndicatorStyle = {}, containerStyle = {}, itemStyle = {}, itemHeight = 40, scaleFunction = (x) => 1.0 ** x, rotationFunction = (x) => 1 - Math.pow(1 / 2, x), opacityFunction = (x) => Math.pow(1 / 3, x), visibleRest = 2, decelerationRate = 'normal', containerProps = {}, flatListProps = {}, }) => {
|
|
7
|
+
const momentumStarted = useRef(false);
|
|
8
|
+
const selectedIndex = options.findIndex(item => item.value === value);
|
|
9
|
+
const flatListRef = useRef(null);
|
|
10
|
+
const [scrollY] = useState(new Animated.Value(selectedIndex * itemHeight));
|
|
11
|
+
const containerHeight = (1 + visibleRest * 2) * itemHeight;
|
|
12
|
+
const paddedOptions = useMemo(() => {
|
|
13
|
+
const array = [...options];
|
|
14
|
+
for (let i = 0; i < visibleRest; i++) {
|
|
15
|
+
array.unshift(null);
|
|
16
|
+
array.push(null);
|
|
17
|
+
}
|
|
18
|
+
return array;
|
|
19
|
+
}, [options, visibleRest]);
|
|
20
|
+
const offsets = useMemo(() => [...Array(paddedOptions.length)].map((_, i) => i * itemHeight), [paddedOptions, itemHeight]);
|
|
21
|
+
const currentScrollIndex = useMemo(() => Animated.add(Animated.divide(scrollY, itemHeight), visibleRest), [visibleRest, scrollY, itemHeight]);
|
|
22
|
+
const handleScrollEnd = (event) => {
|
|
23
|
+
const offsetY = Math.min(itemHeight * (options.length - 1), Math.max(event.nativeEvent.contentOffset.y, 0));
|
|
24
|
+
let index = Math.floor(offsetY / itemHeight);
|
|
25
|
+
const remainder = offsetY % itemHeight;
|
|
26
|
+
if (remainder > itemHeight / 2) {
|
|
27
|
+
index++;
|
|
28
|
+
}
|
|
29
|
+
if (index !== selectedIndex) {
|
|
30
|
+
onChange(options[index]?.value || 0);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const handleMomentumScrollBegin = () => {
|
|
34
|
+
momentumStarted.current = true;
|
|
35
|
+
};
|
|
36
|
+
const handleMomentumScrollEnd = (event) => {
|
|
37
|
+
momentumStarted.current = false;
|
|
38
|
+
handleScrollEnd(event);
|
|
39
|
+
};
|
|
40
|
+
const handleScrollEndDrag = (event) => {
|
|
41
|
+
// Capture the offset value immediately
|
|
42
|
+
const offsetY = event.nativeEvent.contentOffset?.y;
|
|
43
|
+
// We'll start a short timer to see if momentum scroll begins
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
// If momentum scroll hasn't started within the timeout,
|
|
46
|
+
// then it was a slow scroll that won't trigger momentum
|
|
47
|
+
if (!momentumStarted.current && offsetY !== undefined) {
|
|
48
|
+
// Create a synthetic event with just the data we need
|
|
49
|
+
const syntheticEvent = {
|
|
50
|
+
nativeEvent: {
|
|
51
|
+
contentOffset: { y: offsetY },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
handleScrollEnd(syntheticEvent);
|
|
55
|
+
}
|
|
56
|
+
}, 50);
|
|
57
|
+
};
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (selectedIndex < 0 || selectedIndex >= options.length) {
|
|
60
|
+
throw new Error(`Selected index ${selectedIndex} is out of bounds [0, ${options.length - 1}]`);
|
|
61
|
+
}
|
|
62
|
+
}, [selectedIndex, options]);
|
|
63
|
+
/**
|
|
64
|
+
* If selectedIndex is changed from outside (not via onChange) we need to scroll to the specified index.
|
|
65
|
+
* This ensures that what the user sees as selected in the picker always corresponds to the value state.
|
|
66
|
+
*/
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
flatListRef.current?.scrollToIndex({
|
|
69
|
+
index: selectedIndex,
|
|
70
|
+
animated: Platform.OS === 'ios',
|
|
71
|
+
});
|
|
72
|
+
}, [selectedIndex, itemHeight]);
|
|
73
|
+
return (_jsxs(View, { style: [styles.container, { height: containerHeight }, containerStyle], ...containerProps, children: [_jsx(View, { style: [
|
|
74
|
+
styles.selectedIndicator,
|
|
75
|
+
selectedIndicatorStyle,
|
|
76
|
+
{
|
|
77
|
+
transform: [{ translateY: -itemHeight / 2 }],
|
|
78
|
+
height: itemHeight,
|
|
79
|
+
},
|
|
80
|
+
] }), _jsx(Animated.FlatList, { ...flatListProps, ref: flatListRef, nestedScrollEnabled: true, style: styles.scrollView, showsVerticalScrollIndicator: false, onScroll: Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
|
|
81
|
+
useNativeDriver: true,
|
|
82
|
+
}), onScrollEndDrag: handleScrollEndDrag, onMomentumScrollBegin: handleMomentumScrollBegin, onMomentumScrollEnd: handleMomentumScrollEnd, snapToOffsets: offsets, decelerationRate: decelerationRate, initialScrollIndex: selectedIndex, getItemLayout: (_, index) => ({
|
|
83
|
+
length: itemHeight,
|
|
84
|
+
offset: itemHeight * index,
|
|
85
|
+
index,
|
|
86
|
+
}), data: paddedOptions, keyExtractor: (item, index) => item ? `${item.value}-${item.text}-${index}` : `null-${index}`, renderItem: ({ item: option, index }) => (_jsx(WheelPickerItem, { index: index, option: option, style: itemStyle, height: itemHeight, currentScrollIndex: currentScrollIndex, scaleFunction: scaleFunction, rotationFunction: rotationFunction, opacityFunction: opacityFunction, visibleRest: visibleRest }, `option-${index}`)) })] }));
|
|
87
|
+
};
|
|
88
|
+
export default memo(WheelPicker);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
container: {
|
|
3
|
+
position: "relative";
|
|
4
|
+
};
|
|
5
|
+
selectedIndicator: {
|
|
6
|
+
position: "absolute";
|
|
7
|
+
width: "100%";
|
|
8
|
+
top: "50%";
|
|
9
|
+
};
|
|
10
|
+
scrollView: {
|
|
11
|
+
overflow: "hidden";
|
|
12
|
+
flex: number;
|
|
13
|
+
};
|
|
14
|
+
option: {
|
|
15
|
+
alignItems: "center";
|
|
16
|
+
justifyContent: "center";
|
|
17
|
+
paddingHorizontal: number;
|
|
18
|
+
zIndex: number;
|
|
19
|
+
};
|
|
20
|
+
} & {
|
|
21
|
+
useVariants: (variants: never) => void;
|
|
22
|
+
};
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
|
+
export default StyleSheet.create({
|
|
3
|
+
container: {
|
|
4
|
+
position: 'relative',
|
|
5
|
+
},
|
|
6
|
+
selectedIndicator: {
|
|
7
|
+
position: 'absolute',
|
|
8
|
+
width: '100%',
|
|
9
|
+
top: '50%',
|
|
10
|
+
},
|
|
11
|
+
scrollView: {
|
|
12
|
+
overflow: 'hidden',
|
|
13
|
+
flex: 1,
|
|
14
|
+
},
|
|
15
|
+
option: {
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
justifyContent: 'center',
|
|
18
|
+
paddingHorizontal: 16,
|
|
19
|
+
zIndex: 100,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PickerOption } from '../DatePicker.props';
|
|
2
|
+
interface WheelProps {
|
|
3
|
+
value: number | string;
|
|
4
|
+
setValue?: (value: any) => void;
|
|
5
|
+
items: PickerOption[];
|
|
6
|
+
}
|
|
7
|
+
declare const _default: import("react").MemoExoticComponent<({ value, setValue, items }: WheelProps) => import("react/jsx-runtime").JSX.Element>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useMemo, useRef } from 'react';
|
|
3
|
+
import { Animated, PanResponder, Platform, View } from 'react-native';
|
|
4
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
+
import { isEqual } from '../../../utils';
|
|
6
|
+
import { BodyText } from '../../BodyText';
|
|
7
|
+
import { CONTAINER_HEIGHT } from '../enums';
|
|
8
|
+
import { sin } from './animated-math';
|
|
9
|
+
const ITEM_HEIGHT = 44;
|
|
10
|
+
const WheelWeb = ({ value, setValue = () => { }, items }) => {
|
|
11
|
+
const displayCount = 5;
|
|
12
|
+
const translateY = useRef(new Animated.Value(0)).current;
|
|
13
|
+
const renderCount = displayCount * 2 < items.length ? displayCount * 8 : displayCount * 2 - 1;
|
|
14
|
+
const circular = items.length >= displayCount;
|
|
15
|
+
const height = 140;
|
|
16
|
+
const radius = height / 2;
|
|
17
|
+
const valueIndex = useMemo(() => {
|
|
18
|
+
return Math.max(0, items.findIndex(item => item.value === value));
|
|
19
|
+
}, [items, value]);
|
|
20
|
+
const panResponder = useMemo(() => {
|
|
21
|
+
return PanResponder.create({
|
|
22
|
+
onMoveShouldSetPanResponder: () => true,
|
|
23
|
+
onStartShouldSetPanResponderCapture: () => true,
|
|
24
|
+
onPanResponderGrant: () => {
|
|
25
|
+
translateY.setValue(0);
|
|
26
|
+
},
|
|
27
|
+
onPanResponderMove: (evt, gestureState) => {
|
|
28
|
+
translateY.setValue(gestureState.dy);
|
|
29
|
+
evt.stopPropagation();
|
|
30
|
+
},
|
|
31
|
+
onPanResponderRelease: (_, gestureState) => {
|
|
32
|
+
translateY.extractOffset();
|
|
33
|
+
let newValueIndex = valueIndex - Math.round(gestureState.dy / ((radius * 2) / displayCount));
|
|
34
|
+
if (circular) {
|
|
35
|
+
newValueIndex = (newValueIndex + items.length) % items.length;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
if (newValueIndex < 0) {
|
|
39
|
+
newValueIndex = 0;
|
|
40
|
+
}
|
|
41
|
+
else if (newValueIndex >= items.length) {
|
|
42
|
+
newValueIndex = items.length - 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const newValue = items[newValueIndex];
|
|
46
|
+
if (newValue?.value === value) {
|
|
47
|
+
translateY.setOffset(0);
|
|
48
|
+
translateY.setValue(0);
|
|
49
|
+
}
|
|
50
|
+
else if (newValue?.value) {
|
|
51
|
+
setValue(newValue.value);
|
|
52
|
+
}
|
|
53
|
+
else if (items[0]?.value) {
|
|
54
|
+
setValue(items[0].value);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}, [circular, displayCount, radius, setValue, value, valueIndex, items, translateY]);
|
|
59
|
+
const displayValues = useMemo(() => {
|
|
60
|
+
const centerIndex = Math.floor(renderCount / 2);
|
|
61
|
+
return Array.from({ length: renderCount }, (_, index) => {
|
|
62
|
+
let targetIndex = valueIndex + index - centerIndex;
|
|
63
|
+
if (circular) {
|
|
64
|
+
targetIndex = ((targetIndex % items.length) + items.length) % items.length;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
targetIndex = Math.max(0, Math.min(targetIndex, items.length - 1));
|
|
68
|
+
}
|
|
69
|
+
return items[targetIndex] || items[0];
|
|
70
|
+
});
|
|
71
|
+
}, [renderCount, valueIndex, items, circular]);
|
|
72
|
+
const animatedAngles = useMemo(() => {
|
|
73
|
+
//translateY.setValue(0);
|
|
74
|
+
translateY.setOffset(0);
|
|
75
|
+
const currentIndex = displayValues.findIndex(item => item?.value === value);
|
|
76
|
+
return displayValues && displayValues.length > 0
|
|
77
|
+
? displayValues.map((_, index) => translateY
|
|
78
|
+
.interpolate({
|
|
79
|
+
inputRange: [-radius, radius],
|
|
80
|
+
outputRange: [
|
|
81
|
+
-radius + ((radius * 2) / displayCount) * (index - currentIndex),
|
|
82
|
+
radius + ((radius * 2) / displayCount) * (index - currentIndex),
|
|
83
|
+
],
|
|
84
|
+
extrapolate: 'extend',
|
|
85
|
+
})
|
|
86
|
+
.interpolate({
|
|
87
|
+
inputRange: [-radius, radius],
|
|
88
|
+
outputRange: [-Math.PI / 2, Math.PI / 2],
|
|
89
|
+
extrapolate: 'clamp',
|
|
90
|
+
}))
|
|
91
|
+
: [];
|
|
92
|
+
}, [displayValues, radius, value, displayCount, translateY]);
|
|
93
|
+
return (_jsxs(View, { style: [styles.container], ...panResponder.panHandlers, children: [_jsx(View, { style: [
|
|
94
|
+
styles.selectedIndicator,
|
|
95
|
+
{
|
|
96
|
+
transform: [{ translateY: -ITEM_HEIGHT / 2 }],
|
|
97
|
+
height: ITEM_HEIGHT,
|
|
98
|
+
},
|
|
99
|
+
] }), displayValues?.map((displayValue, index) => {
|
|
100
|
+
const animatedAngle = animatedAngles[index];
|
|
101
|
+
return (_jsx(Animated.View, { style: {
|
|
102
|
+
position: 'absolute',
|
|
103
|
+
height: ITEM_HEIGHT - 10,
|
|
104
|
+
transform: animatedAngle
|
|
105
|
+
? [
|
|
106
|
+
{
|
|
107
|
+
translateY: Animated.multiply(radius, sin(animatedAngle)),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
rotateX: animatedAngle.interpolate({
|
|
111
|
+
inputRange: [-Math.PI / 2, Math.PI / 2],
|
|
112
|
+
outputRange: ['-89deg', '89deg'],
|
|
113
|
+
extrapolate: 'clamp',
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
: [],
|
|
118
|
+
opacity: displayValue?.value !== value ? 0.3 : 1,
|
|
119
|
+
}, children: _jsx(BodyText, { children: displayValue?.text }) }, `${displayValue?.text}-${index}`));
|
|
120
|
+
})] }));
|
|
121
|
+
};
|
|
122
|
+
const styles = StyleSheet.create({
|
|
123
|
+
container: {
|
|
124
|
+
minWidth: 30,
|
|
125
|
+
overflow: 'hidden',
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
justifyContent: 'center',
|
|
128
|
+
height: CONTAINER_HEIGHT / 2,
|
|
129
|
+
...Platform.select({
|
|
130
|
+
web: {
|
|
131
|
+
cursor: 'pointer',
|
|
132
|
+
userSelect: 'none',
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
selectedIndicator: {
|
|
137
|
+
position: 'absolute',
|
|
138
|
+
width: '100%',
|
|
139
|
+
top: '50%',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const customComparator = (prev, next) => {
|
|
143
|
+
const areEqual = prev.value === next.value && prev.setValue === next.setValue && isEqual(prev.items, next.items);
|
|
144
|
+
return areEqual;
|
|
145
|
+
};
|
|
146
|
+
export default memo(WheelWeb, customComparator);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PickerOption } from '../DatePicker.props';
|
|
2
|
+
type WheelProps = {
|
|
3
|
+
value: number | string;
|
|
4
|
+
setValue?: (value: any) => void;
|
|
5
|
+
items: PickerOption[];
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("react").MemoExoticComponent<(props: WheelProps) => import("react/jsx-runtime").JSX.Element>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
import WheelNative from './wheel-native';
|
|
5
|
+
import WheelWeb from './wheel-web';
|
|
6
|
+
const Wheel = (props) => {
|
|
7
|
+
const Component = Platform.OS === 'web' ? WheelWeb : WheelNative;
|
|
8
|
+
return _jsx(Component, { ...props });
|
|
9
|
+
};
|
|
10
|
+
export default memo(Wheel);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import SelectProps from './Select.props';
|
|
2
2
|
declare const Select: {
|
|
3
|
-
({ options, value, onValueChange, label, labelVariant, placeholder, disabled, leadingIcon: LeadingIcon, validationStatus, helperText, helperIcon, invalidText, validText, required, children, bottomSheetProps, menuHeading, readonly, emptyText, listProps, searchable, searchPlaceholder, ...rest }: SelectProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
({ options, value, onValueChange, label, labelVariant, placeholder, disabled, leadingIcon: LeadingIcon, validationStatus, helperText, helperIcon, invalidText, validText, required, children, bottomSheetProps, menuHeading, readonly, emptyText, listProps, searchable, searchPlaceholder, testID, ...rest }: SelectProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
displayName: string;
|
|
5
5
|
};
|
|
6
6
|
export default Select;
|
|
@@ -12,7 +12,7 @@ import { Input } from '../Input';
|
|
|
12
12
|
import { SelectContext } from './Select.context';
|
|
13
13
|
import SelectOption from './SelectOption';
|
|
14
14
|
import { SafeAreaView } from '../SafeAreaView';
|
|
15
|
-
const Select = ({ options = [], value, onValueChange, label, labelVariant = 'body', placeholder = 'Select an option', disabled = false, leadingIcon: LeadingIcon, validationStatus = 'initial', helperText, helperIcon, invalidText, validText, required = true, children, bottomSheetProps, menuHeading, readonly = false, emptyText = 'No options available', listProps, searchable = false, searchPlaceholder = 'Search', ...rest }) => {
|
|
15
|
+
const Select = ({ options = [], value, onValueChange, label, labelVariant = 'body', placeholder = 'Select an option', disabled = false, leadingIcon: LeadingIcon, validationStatus = 'initial', helperText, helperIcon, invalidText, validText, required = true, children, bottomSheetProps, menuHeading, readonly = false, emptyText = 'No options available', listProps, searchable = false, searchPlaceholder = 'Search', testID, ...rest }) => {
|
|
16
16
|
const formFieldContext = useFormFieldContext();
|
|
17
17
|
const validationStatusFromContext = formFieldContext?.validationStatus ?? validationStatus;
|
|
18
18
|
const isRequired = formFieldContext?.required ?? required;
|
|
@@ -65,9 +65,9 @@ const Select = ({ options = [], value, onValueChange, label, labelVariant = 'bod
|
|
|
65
65
|
setIsOpen(false);
|
|
66
66
|
setSearch('');
|
|
67
67
|
}, []);
|
|
68
|
-
const renderSelectOption = useCallback(({ item }) => (_jsx(SelectOption, { label: item.label, value: item.value, disabled: item.disabled, leadingIcon: item.leadingIcon, trailingIcon: item.trailingIcon })), []);
|
|
68
|
+
const renderSelectOption = useCallback(({ item }) => (_jsx(SelectOption, { label: item.label, value: item.value, disabled: item.disabled, leadingIcon: item.leadingIcon, trailingIcon: item.trailingIcon, testID: testID ? `${testID}-option-${item.label}` : undefined })), [testID]);
|
|
69
69
|
const renderEmptyComponent = useCallback(() => (_jsx(BottomSheetView, { style: styles.emptyContainer, children: _jsx(DetailText, { children: emptyText }) })), [emptyText]);
|
|
70
|
-
return (_jsxs(View, { ...rest, style: [styles.container, rest.style], children: [_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validationStatus: validationStatusFromContext, required: isRequired, disabled: isDisabled, readonly: isReadonly, invalidText: invalidText, validText: validText, children: _jsxs(Pressable, { onPress: openBottomSheet, disabled: isDisabled || isReadonly, style: ({ pressed }) => [
|
|
70
|
+
return (_jsxs(View, { ...rest, style: [styles.container, rest.style], children: [_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validationStatus: validationStatusFromContext, required: isRequired, disabled: isDisabled, readonly: isReadonly, invalidText: invalidText, validText: validText, children: _jsxs(Pressable, { onPress: openBottomSheet, disabled: isDisabled || isReadonly, testID: testID, style: ({ pressed }) => [
|
|
71
71
|
styles.selectContainer,
|
|
72
72
|
styles.pressedContainer(pressed || isOpen),
|
|
73
73
|
], children: [!!LeadingIcon && (_jsx(View, { children: (() => {
|
|
@@ -77,7 +77,7 @@ const Select = ({ options = [], value, onValueChange, label, labelVariant = 'bod
|
|
|
77
77
|
selectedValue: value,
|
|
78
78
|
onValueChange,
|
|
79
79
|
close: closeBottomSheet,
|
|
80
|
-
}, children: _jsxs(SafeAreaView, { edges: ['top'], style: { flex: 1 }, children: [menuHeading && (_jsx(View, { style: styles.headingContainer, children: _jsx(DetailText, { size: "lg", children: menuHeading }) })), searchable && (_jsx(View, { style: styles.searchContainer, children: _jsx(Input, { placeholder: searchPlaceholder, value: search, inBottomSheet: true, onChangeText: setSearch, type: "search" }) })), children ? (_jsx(BottomSheetScrollView, { children: children })) : (_jsx(BottomSheetFlatList, { data: filteredOptions, keyExtractor: (option) => option.value, renderItem: renderSelectOption, ListEmptyComponent: renderEmptyComponent, ...listProps }))] }) }) })] }));
|
|
80
|
+
}, children: _jsxs(SafeAreaView, { edges: ['top'], style: { flex: 1 }, children: [menuHeading && (_jsx(View, { style: styles.headingContainer, children: _jsx(DetailText, { size: "lg", children: menuHeading }) })), searchable && (_jsx(View, { style: styles.searchContainer, children: _jsx(Input, { placeholder: searchPlaceholder, value: search, inBottomSheet: true, onChangeText: setSearch, type: "search", testID: testID ? `${testID}-search` : undefined }) })), children ? (_jsx(BottomSheetScrollView, { testID: testID ? `${testID}-options` : undefined, children: children })) : (_jsx(BottomSheetFlatList, { data: filteredOptions, keyExtractor: (option) => option.value, renderItem: renderSelectOption, ListEmptyComponent: renderEmptyComponent, testID: testID ? `${testID}-options` : undefined, ...listProps }))] }) }) })] }));
|
|
81
81
|
};
|
|
82
82
|
const styles = StyleSheet.create(theme => ({
|
|
83
83
|
container: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SelectOptionProps } from './Select.props';
|
|
2
2
|
declare const SelectOption: {
|
|
3
|
-
({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, }: SelectOptionProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, testID, }: SelectOptionProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
displayName: string;
|
|
5
5
|
};
|
|
6
6
|
export default SelectOption;
|
|
@@ -5,7 +5,7 @@ import { StyleSheet } from 'react-native-unistyles';
|
|
|
5
5
|
import { BodyText } from '../BodyText';
|
|
6
6
|
import { Icon } from '../Icon';
|
|
7
7
|
import { useSelectContext } from './Select.context';
|
|
8
|
-
const SelectOption = ({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, }) => {
|
|
8
|
+
const SelectOption = ({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, testID, }) => {
|
|
9
9
|
const { selectedValue, onValueChange, close } = useSelectContext();
|
|
10
10
|
const isSelected = selected !== undefined ? selected : selectedValue === value;
|
|
11
11
|
styles.useVariants({ disabled });
|
|
@@ -22,7 +22,7 @@ const SelectOption = ({ label, value, leadingIcon: LeftIcon, trailingIcon: Right
|
|
|
22
22
|
close();
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
-
return (_jsxs(Pressable, { onPress: handlePress, disabled: disabled, style: ({ pressed }) => [styles.container, pressed && styles.pressed], children: [!!LeftIcon && (_jsx(View, { children: _jsx(Icon, { as: LeftIcon, style: styles.icon }) })), _jsx(View, { style: styles.labelContainer, children: _jsx(BodyText, { children: label }) }), isSelected && (_jsx(View, { children: _jsx(Icon, { as: TickSmallIcon, style: styles.icon }) })), !!RightIcon && !isSelected && (_jsx(View, { children: _jsx(Icon, { as: RightIcon, style: styles.icon }) }))] }));
|
|
25
|
+
return (_jsxs(Pressable, { onPress: handlePress, disabled: disabled, testID: testID, style: ({ pressed }) => [styles.container, pressed && styles.pressed], children: [!!LeftIcon && (_jsx(View, { children: _jsx(Icon, { as: LeftIcon, style: styles.icon }) })), _jsx(View, { style: styles.labelContainer, children: _jsx(BodyText, { children: label }) }), isSelected && (_jsx(View, { children: _jsx(Icon, { as: TickSmallIcon, style: styles.icon }) })), !!RightIcon && !isSelected && (_jsx(View, { children: _jsx(Icon, { as: RightIcon, style: styles.icon }) }))] }));
|
|
26
26
|
};
|
|
27
27
|
const styles = StyleSheet.create(theme => ({
|
|
28
28
|
container: {
|
|
@@ -5,7 +5,7 @@ import { StyleSheet } from 'react-native-unistyles';
|
|
|
5
5
|
import { FormField } from '../FormField';
|
|
6
6
|
import { getNextIndexFromValueChange } from './VerificationInput.utils';
|
|
7
7
|
import { VerificationInputSlot } from './VerificationInputSlot';
|
|
8
|
-
const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVariant = 'body', helperText, helperIcon, validationStatus = 'initial', validText, invalidText, disabled = false, readonly = false, secureTextEntry = false, autoFocus = false, style, ...props }, ref) => {
|
|
8
|
+
const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVariant = 'body', helperText, helperIcon, validationStatus = 'initial', validText, invalidText, disabled = false, readonly = false, secureTextEntry = false, autoFocus = false, style, testID, ...props }, ref) => {
|
|
9
9
|
const length = 6;
|
|
10
10
|
const inputRef = useRef(null);
|
|
11
11
|
const latestValueRef = useRef(value);
|
|
@@ -160,7 +160,7 @@ const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVa
|
|
|
160
160
|
latestSelectionRef.current = nextSelection;
|
|
161
161
|
setSelection(nextSelection);
|
|
162
162
|
setFocusedIndex(Math.min(nextSelection.start, length - 1));
|
|
163
|
-
}, onFocus: handleFocus, onBlur: handleBlur, selection: selection, keyboardType: "number-pad", textContentType: "oneTimeCode", autoComplete: "sms-otp", secureTextEntry: secureTextEntry, maxLength: length, caretHidden: true, style: styles.hiddenInput }), slots.map(index => {
|
|
163
|
+
}, onFocus: handleFocus, onBlur: handleBlur, selection: selection, keyboardType: "number-pad", textContentType: "oneTimeCode", autoComplete: "sms-otp", secureTextEntry: secureTextEntry, maxLength: length, caretHidden: true, style: styles.hiddenInput, testID: testID }), slots.map(index => {
|
|
164
164
|
const char = displayValue[index] || '';
|
|
165
165
|
const isActive = focusedIndex === index;
|
|
166
166
|
const displayChar = secureTextEntry && char ? '*' : char;
|
|
@@ -168,7 +168,7 @@ const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVa
|
|
|
168
168
|
pendingFocusIndexRef.current = index;
|
|
169
169
|
inputRef.current?.focus();
|
|
170
170
|
setSelectionIndex(index);
|
|
171
|
-
} }, index));
|
|
171
|
+
}, testID: testID ? `${testID}-${index}` : undefined }, index));
|
|
172
172
|
})] }) }));
|
|
173
173
|
});
|
|
174
174
|
const styles = StyleSheet.create(theme => ({
|
package/docs/all-components.mdx
CHANGED
|
@@ -12,8 +12,8 @@ This page showcases all the components available in the Hearth React Native libr
|
|
|
12
12
|
<AllComponents />
|
|
13
13
|
|
|
14
14
|
<NextPrevPage
|
|
15
|
-
prevLink="
|
|
15
|
+
prevLink="layout-components"
|
|
16
16
|
prevTitle="Layout Components"
|
|
17
|
-
nextLink="
|
|
17
|
+
nextLink="components-accordion"
|
|
18
18
|
nextTitle="Accordion"
|
|
19
19
|
/>
|
package/docs/changelog.mdx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
-
import { BackToTopButton } from './components';
|
|
2
|
+
import { BackToTopButton, NextPrevPage } from './components';
|
|
3
3
|
|
|
4
4
|
<Meta title="Changelog" />
|
|
5
5
|
<BackToTopButton />
|
|
@@ -9,6 +9,176 @@ import { BackToTopButton } from './components';
|
|
|
9
9
|
The changelog for the Hearth React Native library. Here you can find all the changes, improvements, and bug fixes for each version.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
## 0.28.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [#1018](https://github.com/utilitywarehouse/hearth/pull/1018) [`1c5e02e`](https://github.com/utilitywarehouse/hearth/commit/1c5e02ea4b61329e7c55e52f9aa4ae44abc0da23) Thanks [@fillyD](https://github.com/fillyD)! - 🐛 [FIX]: Adds missing `testID` to `Select`, `SelectOption` and `VerificationInput` component
|
|
17
|
+
|
|
18
|
+
## 0.28.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- [#1014](https://github.com/utilitywarehouse/hearth/pull/1014) [`c10ff82`](https://github.com/utilitywarehouse/hearth/commit/c10ff82243265217acd95f687d48d803b3c7a4bd) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `Combobox` and `SafeAreaView` to the React Native library
|
|
23
|
+
|
|
24
|
+
The React Native package now includes a `Combobox` component for searchable selection in a bottom sheet, plus a `SafeAreaView` primitive that applies Unistyles runtime insets only when a view actually overlaps a screen edge. `Combobox` supports the built-in options API for fixed lists, controlled search values for dynamic results, and custom bottom sheet content for cases where you need to bring your own `BottomSheetFlatList` or bespoke option layout. `Modal` now uses `SafeAreaView` in its full-screen navigation modal path so content like search inputs no longer sits behind the dynamic island on iOS.
|
|
25
|
+
|
|
26
|
+
**Components affected**:
|
|
27
|
+
- `Combobox`
|
|
28
|
+
- `ComboboxOption`
|
|
29
|
+
- `SafeAreaView`
|
|
30
|
+
- `Modal`
|
|
31
|
+
|
|
32
|
+
**Developer changes**:
|
|
33
|
+
|
|
34
|
+
Import the new components from `@utilitywarehouse/hearth-react-native` and choose the API that fits your layout. Use `options` for straightforward searchable lists, render custom sheet content when you need virtualised or dynamic results, and wrap full-screen content in `SafeAreaView` when it should only pick up edge insets if it actually touches that edge.
|
|
35
|
+
|
|
36
|
+
- [`8e37595`](https://github.com/utilitywarehouse/hearth/commit/8e375958559357ce5c1703505fa7438887d9e18e) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `Pagination` component
|
|
37
|
+
|
|
38
|
+
The package now includes a `Pagination` component for moving between pages of content. It supports numbered pagination, a condensed layout for smaller spaces, optional skip buttons for jumping to the first and last page, and controlled page state so it can be wired into lists, tables, or other paged views.
|
|
39
|
+
|
|
40
|
+
**Components affected**:
|
|
41
|
+
- `Pagination`
|
|
42
|
+
|
|
43
|
+
**Developer changes**:
|
|
44
|
+
|
|
45
|
+
Import `Pagination` from `@utilitywarehouse/hearth-react-native` and control the current page in your screen or feature state.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { useState } from 'react';
|
|
49
|
+
import { Pagination } from '@utilitywarehouse/hearth-react-native';
|
|
50
|
+
|
|
51
|
+
const MyComponent = () => {
|
|
52
|
+
const [page, setPage] = useState(1);
|
|
53
|
+
|
|
54
|
+
return <Pagination currentPage={page} onPageChange={setPage} totalPages={10} />;
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- [`8e37595`](https://github.com/utilitywarehouse/hearth/commit/8e375958559357ce5c1703505fa7438887d9e18e) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `Table` component
|
|
59
|
+
|
|
60
|
+
The package now includes a composable `Table` API for presenting structured data with headers, rows, cells, optional card-style containers, horizontal scrolling for narrow viewports, configurable column widths, and pagination support through `TablePagination`. Header cells support trailing actions such as sort controls, and the API is split into smaller building blocks so layouts can be assembled to fit different datasets.
|
|
61
|
+
|
|
62
|
+
**Components affected**:
|
|
63
|
+
- `Table`
|
|
64
|
+
- `TableHeader`
|
|
65
|
+
- `TableHeaderCell`
|
|
66
|
+
- `TableBody`
|
|
67
|
+
- `TableRow`
|
|
68
|
+
- `TableCell`
|
|
69
|
+
- `TablePagination`
|
|
70
|
+
|
|
71
|
+
**Developer changes**:
|
|
72
|
+
|
|
73
|
+
Import the table primitives from `@utilitywarehouse/hearth-react-native` and compose them to match your data shape. Add `columnWidths` when you need fixed or weighted columns, and pass `pagination` when rows should be paged.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import {
|
|
77
|
+
Table,
|
|
78
|
+
TableBody,
|
|
79
|
+
TableCell,
|
|
80
|
+
TableHeader,
|
|
81
|
+
TableHeaderCell,
|
|
82
|
+
TableRow,
|
|
83
|
+
} from '@utilitywarehouse/hearth-react-native';
|
|
84
|
+
|
|
85
|
+
const MyComponent = () => (
|
|
86
|
+
<Table columnWidths={[180, '2fr', '1fr']} container="subtle">
|
|
87
|
+
<TableHeader color="purple">
|
|
88
|
+
<TableHeaderCell>Name</TableHeaderCell>
|
|
89
|
+
<TableHeaderCell>Email</TableHeaderCell>
|
|
90
|
+
<TableHeaderCell>Status</TableHeaderCell>
|
|
91
|
+
</TableHeader>
|
|
92
|
+
<TableBody>
|
|
93
|
+
<TableRow>
|
|
94
|
+
<TableHeaderCell row>Alex Morgan</TableHeaderCell>
|
|
95
|
+
<TableCell>alex@example.com</TableCell>
|
|
96
|
+
<TableCell>Active</TableCell>
|
|
97
|
+
</TableRow>
|
|
98
|
+
</TableBody>
|
|
99
|
+
</Table>
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- [#1016](https://github.com/utilitywarehouse/hearth/pull/1016) [`33baa9e`](https://github.com/utilitywarehouse/hearth/commit/33baa9e8edb091bbd1d17c9a3838352a7f1b87ea) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Adds `Timeline` and `TimelineItem`
|
|
104
|
+
|
|
105
|
+
The package now includes `Timeline` and `TimelineItem` components for showing a sequence of static stops or progress steps. The new API supports labelled items, optional helper text, progress states for complete or active steps, and custom content within an item when you need to show extra context or actions.
|
|
106
|
+
|
|
107
|
+
**Components affected**:
|
|
108
|
+
- `Timeline`
|
|
109
|
+
- `TimelineItem`
|
|
110
|
+
|
|
111
|
+
**Developer changes**:
|
|
112
|
+
|
|
113
|
+
Import `Timeline` and `TimelineItem` from `@utilitywarehouse/hearth-react-native`. Use `variant="static"` for simple ordered events, or `variant="progress"` with item `state` values to communicate step progress.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { Timeline, TimelineItem } from '@utilitywarehouse/hearth-react-native';
|
|
117
|
+
|
|
118
|
+
const MyComponent = () => (
|
|
119
|
+
<Timeline variant="progress">
|
|
120
|
+
<TimelineItem label="Ordered" helperText="We have received your order" state="complete" />
|
|
121
|
+
<TimelineItem label="Packed" helperText="Your items are ready" state="complete" />
|
|
122
|
+
<TimelineItem label="Out for delivery" helperText="Arriving today" state="active" />
|
|
123
|
+
<TimelineItem label="Delivered" helperText="Pending" state="incomplete" />
|
|
124
|
+
</Timeline>
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 0.27.3
|
|
129
|
+
|
|
130
|
+
### Patch Changes
|
|
131
|
+
|
|
132
|
+
- [#1006](https://github.com/utilitywarehouse/hearth/pull/1006) [`1996112`](https://github.com/utilitywarehouse/hearth/commit/1996112864146e86972ef6b9b07a8be5a72b552f) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Make `paddingNone` remove horizontal padding for ghost buttons in `md` size
|
|
133
|
+
|
|
134
|
+
Fixed an issue where setting `paddingNone` on a ghost button did not remove horizontal padding when using `md` size. The prop now removes horizontal padding for both `sm` and `md` ghost buttons.
|
|
135
|
+
|
|
136
|
+
**Components affected**:
|
|
137
|
+
- `Button`
|
|
138
|
+
|
|
139
|
+
**Developer changes**:
|
|
140
|
+
|
|
141
|
+
No changes required.
|
|
142
|
+
|
|
143
|
+
- [#1012](https://github.com/utilitywarehouse/hearth/pull/1012) [`4fda116`](https://github.com/utilitywarehouse/hearth/commit/4fda116c2a1bec383df7e630180ab57166ab9da4) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Prevent outlines from being clipped for scrollable children in `Modal`
|
|
144
|
+
|
|
145
|
+
Fixed an issue in in-nav modals where child components with outlines could be visually clipped at the horizontal edges when content was scrollable.
|
|
146
|
+
|
|
147
|
+
**Components affected**:
|
|
148
|
+
- `Modal`
|
|
149
|
+
|
|
150
|
+
**Developer changes**:
|
|
151
|
+
|
|
152
|
+
No changes required.
|
|
153
|
+
|
|
154
|
+
- [#1012](https://github.com/utilitywarehouse/hearth/pull/1012) [`4fda116`](https://github.com/utilitywarehouse/hearth/commit/4fda116c2a1bec383df7e630180ab57166ab9da4) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Update horizontal padding values for scrollable in-nav `Modal` content
|
|
155
|
+
|
|
156
|
+
Adjusted horizontal padding behaviour in scrollable in-nav modals to preserve child outlines while keeping visual spacing consistent.
|
|
157
|
+
|
|
158
|
+
**Components affected**:
|
|
159
|
+
- `Modal`
|
|
160
|
+
|
|
161
|
+
**Developer changes**:
|
|
162
|
+
|
|
163
|
+
No changes required.
|
|
164
|
+
|
|
165
|
+
- [#1009](https://github.com/utilitywarehouse/hearth/pull/1009) [`3d65ef2`](https://github.com/utilitywarehouse/hearth/commit/3d65ef2f8f7701b128a9c679f1910cd3d0f5c0c3) Thanks [@fillyD](https://github.com/fillyD)! - 🐛 [FIX]: testID for `List` component
|
|
166
|
+
|
|
167
|
+
## 0.27.2
|
|
168
|
+
|
|
169
|
+
### Patch Changes
|
|
170
|
+
|
|
171
|
+
- [#1003](https://github.com/utilitywarehouse/hearth/pull/1003) [`cdb95ea`](https://github.com/utilitywarehouse/hearth/commit/cdb95eabb279adaf348487ae3fb4a20e600e039e) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Correct `VerificationInput` focus progression after editing an empty slot
|
|
172
|
+
|
|
173
|
+
Fixed an issue where entering a value after selecting an empty verification slot could move focus to the wrong slot. Focus now moves to the slot immediately after the one that was actually updated.
|
|
174
|
+
|
|
175
|
+
**Components affected**:
|
|
176
|
+
- `VerificationInput`
|
|
177
|
+
|
|
178
|
+
**Developer changes**:
|
|
179
|
+
|
|
180
|
+
No changes required.
|
|
181
|
+
|
|
12
182
|
## 0.27.1
|
|
13
183
|
|
|
14
184
|
### Patch Changes
|
|
@@ -1255,3 +1425,10 @@ The changelog for the Hearth React Native library. Here you can find all the cha
|
|
|
1255
1425
|
|
|
1256
1426
|
- [`f6366c4`](https://github.com/utilitywarehouse/hearth/commit/f6366c4da2676c535dca90be570d6e6bae5a0349) Thanks [@jordmccord](https://github.com/jordmccord)! - Initial Release 🎉
|
|
1257
1427
|
|
|
1428
|
+
|
|
1429
|
+
<NextPrevPage
|
|
1430
|
+
prevLink="getting-started"
|
|
1431
|
+
prevTitle="Getting Started"
|
|
1432
|
+
nextLink="styling"
|
|
1433
|
+
nextTitle="Styling"
|
|
1434
|
+
/>
|