@utilitywarehouse/hearth-react-native 0.27.0 → 0.27.2-test
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/.storybook/vitest.setup.ts +35 -3
- package/.turbo/turbo-build.log +5 -4
- package/CHANGELOG.md +42 -0
- package/build/components/Carousel/Carousel.js +6 -1
- package/build/components/List/List.js +2 -2
- package/build/components/Modal/Modal.js +1 -1
- package/build/components/SegmentedControl/SegmentedControl.js +4 -1
- package/build/components/SegmentedControl/SegmentedControlOption.js +24 -2
- package/build/components/SegmentedControl/SegmentedControlOption.props.d.ts +3 -1
- package/build/components/TimePicker/TimePickerWheel.js +9 -1
- package/build/components/Toast/Toast.context.js +1 -1
- package/build/components/VerificationInput/VerificationInput.js +10 -21
- package/build/components/VerificationInput/VerificationInput.utils.d.ts +8 -0
- package/build/components/VerificationInput/VerificationInput.utils.js +17 -0
- package/build/components/VerificationInput/VerificationInput.utils.test.d.ts +1 -0
- package/build/components/VerificationInput/VerificationInput.utils.test.js +36 -0
- package/docs/changelog.mdx +113 -0
- package/package.json +6 -5
- package/src/components/Carousel/Carousel.tsx +6 -2
- package/src/components/IconContainer/IconContainer.stories.tsx +35 -30
- package/src/components/List/List.tsx +5 -4
- package/src/components/Modal/Modal.tsx +1 -1
- package/src/components/SegmentedControl/SegmentedControl.docs.mdx +42 -15
- package/src/components/SegmentedControl/SegmentedControl.figma.tsx +8 -9
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +21 -0
- package/src/components/SegmentedControl/SegmentedControl.tsx +4 -1
- package/src/components/SegmentedControl/SegmentedControlOption.props.ts +3 -1
- package/src/components/SegmentedControl/SegmentedControlOption.tsx +58 -34
- package/src/components/Select/SelectOption.figma.tsx +2 -2
- package/src/components/TimePicker/TimePickerWheel.tsx +11 -4
- package/src/components/Toast/Toast.context.tsx +1 -1
- package/src/components/VerificationInput/VerificationInput.stories.tsx +33 -0
- package/src/components/VerificationInput/VerificationInput.tsx +16 -29
- package/src/components/VerificationInput/VerificationInput.utils.test.ts +48 -0
- package/src/components/VerificationInput/VerificationInput.utils.ts +32 -0
- package/tsconfig.eslint.json +2 -1
- package/vitest.config.js +11 -13
- package/vitest.unit.config.ts +9 -0
- package/.turbo/turbo-lint.log +0 -72
|
@@ -1,7 +1,39 @@
|
|
|
1
|
-
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
|
|
2
1
|
import { setProjectAnnotations } from '@storybook/react-native-web-vite';
|
|
3
|
-
import
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// react-native-unistyles/mocks relies on Jest globals.
|
|
5
|
+
if (!(globalThis as { jest?: unknown }).jest) {
|
|
6
|
+
(globalThis as any).jest = vi;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
await import('react-native-unistyles/mocks');
|
|
10
|
+
const { StyleSheet } = await import('react-native-unistyles');
|
|
11
|
+
const { breakpoints } = await import('../src/core/breakpoints');
|
|
12
|
+
const { themes } = await import('../src/core/themes');
|
|
13
|
+
|
|
14
|
+
vi.mock('../src/core', async () => {
|
|
15
|
+
const unistyles = await import('react-native-unistyles');
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
breakpoints,
|
|
19
|
+
themes,
|
|
20
|
+
StyleSheet: unistyles.StyleSheet,
|
|
21
|
+
UnistylesRuntime: unistyles.UnistylesRuntime,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
StyleSheet.configure({
|
|
26
|
+
breakpoints,
|
|
27
|
+
themes,
|
|
28
|
+
settings: {
|
|
29
|
+
initialTheme: 'light',
|
|
30
|
+
adaptiveThemes: false,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const a11yAddonAnnotations = await import('@storybook/addon-a11y/preview');
|
|
35
|
+
const projectAnnotations = await import('./preview');
|
|
4
36
|
|
|
5
37
|
// This is an important step to apply the right configuration when testing your stories.
|
|
6
38
|
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
|
|
7
|
-
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
|
|
39
|
+
setProjectAnnotations([a11yAddonAnnotations as any, projectAnnotations as any]);
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @utilitywarehouse/hearth-react-native@0.27.2-test build /Users/filmondaniels/Projects/Work/hearth/packages/react-native
|
|
4
|
+
> tsc
|
|
5
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @utilitywarehouse/hearth-react-native
|
|
2
2
|
|
|
3
|
+
## 0.27.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#990](https://github.com/utilitywarehouse/hearth/pull/990) [`958e0e1`](https://github.com/utilitywarehouse/hearth/commit/958e0e1a9d5451d1e11fecadc69ae3c5ad9d42ca) Thanks [@declanelcocks](https://github.com/declanelcocks)! - 🐛 [FIX]: Fix `Modal` layout when `inNavModal` and `stickyFooter={false}`.
|
|
8
|
+
|
|
9
|
+
Corrects the container flex style for `inNavModal` modals with a non-sticky footer, where the UX was not great when scrolling.
|
|
10
|
+
|
|
11
|
+
**Components affected**:
|
|
12
|
+
- `Modal`
|
|
13
|
+
|
|
14
|
+
**Developer changes**:
|
|
15
|
+
|
|
16
|
+
No changes required.
|
|
17
|
+
|
|
18
|
+
- [#992](https://github.com/utilitywarehouse/hearth/pull/992) [`2560b3d`](https://github.com/utilitywarehouse/hearth/commit/2560b3dcba7ed4981fad585628f96afd07d8de4f) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Add optional leading `icon` support to `SegmentedControlOption`.
|
|
19
|
+
|
|
20
|
+
This adds an optional `icon` prop to `SegmentedControlOption`, allowing icons to be displayed before option labels in segmented controls.
|
|
21
|
+
|
|
22
|
+
Docs and stories were updated to include icon usage examples.
|
|
23
|
+
|
|
24
|
+
**Components affected**:
|
|
25
|
+
- `SegmentedControlOption`
|
|
26
|
+
|
|
27
|
+
**Developer changes**:
|
|
28
|
+
|
|
29
|
+
No changes required for existing usage.
|
|
30
|
+
|
|
31
|
+
To use the new optional icon prop:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { SegmentedControl, SegmentedControlOption } from '@utilitywarehouse/hearth-react-native';
|
|
35
|
+
import { ElectricitySmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
36
|
+
|
|
37
|
+
<SegmentedControl defaultValue="energy">
|
|
38
|
+
<SegmentedControlOption value="energy" icon={ElectricitySmallIcon}>
|
|
39
|
+
Energy
|
|
40
|
+
</SegmentedControlOption>
|
|
41
|
+
<SegmentedControlOption value="broadband">Broadband</SegmentedControlOption>
|
|
42
|
+
</SegmentedControl>;
|
|
43
|
+
```
|
|
44
|
+
|
|
3
45
|
## 0.27.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
|
@@ -213,7 +213,12 @@ const Carousel = ({ centered = false, children, disabled = false, inactiveItemOp
|
|
|
213
213
|
const controls = (_jsx(CarouselControls, { style: [styles.controls, controlsStyle], itemStyle: controlsItemStyle, activeItemStyle: controlsActiveItemStyle, showNavigation: showNavigation, accessibilityHidden: controlsAccessibilityHidden }));
|
|
214
214
|
// Render for web using ScrollView with scroll snap
|
|
215
215
|
if (isWeb) {
|
|
216
|
-
return (_jsx(CarouselContext.Provider, { value: context, children: _jsxs(View, { style: style, children: [_jsx(ScrollView, { horizontal: true, onScroll: handleWebScroll, onMomentumScrollEnd: handleWebScrollEnd, onScrollEndDrag: handleWebScrollEnd, ref: scrollViewRef, scrollEnabled: !disabled,
|
|
216
|
+
return (_jsx(CarouselContext.Provider, { value: context, children: _jsxs(View, { style: style, children: [_jsx(ScrollView, { horizontal: true, onScroll: handleWebScroll, onMomentumScrollEnd: handleWebScrollEnd, onScrollEndDrag: handleWebScrollEnd, ref: scrollViewRef, scrollEnabled: !disabled, scrollEventThrottle: 16, showsHorizontalScrollIndicator: false, snapToInterval: itemWidth || width, snapToAlignment: centered ? 'center' : 'start', decelerationRate: "fast", style: [
|
|
217
|
+
styles.webContainer,
|
|
218
|
+
webContainerStyles,
|
|
219
|
+
itemsStyle,
|
|
220
|
+
{ pointerEvents: disabled ? 'none' : 'auto' },
|
|
221
|
+
], contentContainerStyle: [styles.webContentContainer, webContentContainerStyle], ...props, children: carouselItems.map((item, index) => cloneElement(item, {
|
|
217
222
|
active: index === activeIndex,
|
|
218
223
|
inactiveOpacity: inactiveItemOpacity,
|
|
219
224
|
key: item?.key || item.props?.id || index,
|
|
@@ -6,7 +6,7 @@ import { Card } from '../Card';
|
|
|
6
6
|
import { SectionHeader } from '../SectionHeader';
|
|
7
7
|
import { ListContext } from './List.context';
|
|
8
8
|
const List = ({ children, heading, helperText, headerTrailingContent, invalidText, ...props }) => {
|
|
9
|
-
const { loading, disabled, container = 'none' } = props;
|
|
9
|
+
const { loading, disabled, container = 'none', testID, style, ...rest } = props;
|
|
10
10
|
const orderRef = useRef([]);
|
|
11
11
|
const [firstItemId, setFirstItemId] = useState(undefined);
|
|
12
12
|
const containerToCard = {
|
|
@@ -35,7 +35,7 @@ const List = ({ children, heading, helperText, headerTrailingContent, invalidTex
|
|
|
35
35
|
registerItem,
|
|
36
36
|
};
|
|
37
37
|
styles.useVariants({ disabled });
|
|
38
|
-
return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...
|
|
38
|
+
return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...rest, style: [styles.container, style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent, invalidText: invalidText })) : null, container === 'none' ? (_jsx(View, { testID: testID, children: children })) : (React.Children.count(children) > 0 && (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, testID: testID, children: _jsx(_Fragment, { children: children }) })))] }) }));
|
|
39
39
|
};
|
|
40
40
|
List.displayName = 'List';
|
|
41
41
|
const styles = StyleSheet.create(theme => ({
|
|
@@ -120,7 +120,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
120
120
|
});
|
|
121
121
|
const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: isBrandBackground && inNavModal, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: isBrandBackground && inNavModal, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
|
|
122
122
|
const InNavModalContainer = scrollable ? ScrollView : View;
|
|
123
|
-
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground && inNavModal, children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground && inNavModal, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] })] })) : null, inNavModal && (_jsxs(InNavModalContainer, { style: {
|
|
123
|
+
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground && inNavModal, children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground && inNavModal, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] })] })) : null, inNavModal && (_jsxs(InNavModalContainer, { style: { flex: stickyFooter ? 1 : 0 }, children: [children, !stickyFooter ? _jsx(View, { style: styles.inNavModalFooterContainer, children: footer }) : null] })), !inNavModal && children, ((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons ? footer : null] })) }));
|
|
124
124
|
const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
|
|
125
125
|
onPressPrimaryButton,
|
|
126
126
|
primaryButtonText,
|
|
@@ -153,7 +153,7 @@ const SegmentedControl = ({ value: controlledValue, defaultValue, onValueChange,
|
|
|
153
153
|
size,
|
|
154
154
|
registerOptionLayout,
|
|
155
155
|
}), [currentValue, select, disabled, size, registerOptionLayout]);
|
|
156
|
-
return (_jsx(SegmentedControlContext.Provider, { value: contextValue, children: _jsxs(View, { accessibilityRole: "radiogroup", accessibilityState: { disabled }, style: [styles.container, computedStyles, style], ...remainingProps, children: [hasIndicator ? (_jsx(Indicator, {
|
|
156
|
+
return (_jsx(SegmentedControlContext.Provider, { value: contextValue, children: _jsxs(View, { accessibilityRole: "radiogroup", accessibilityState: { disabled }, style: [styles.container, computedStyles, style], ...remainingProps, children: [hasIndicator ? (_jsx(Indicator, { style: [styles.indicator, styles.pointerEventsNone, indicatorStyle] })) : null, children] }) }));
|
|
157
157
|
};
|
|
158
158
|
SegmentedControl.displayName = 'SegmentedControl';
|
|
159
159
|
const styles = StyleSheet.create(theme => ({
|
|
@@ -192,5 +192,8 @@ const styles = StyleSheet.create(theme => ({
|
|
|
192
192
|
borderRadius: theme.components.segmentedControl.borderRadius,
|
|
193
193
|
backgroundColor: theme.color.interactive.brand.surface.strong.default,
|
|
194
194
|
},
|
|
195
|
+
pointerEventsNone: {
|
|
196
|
+
pointerEvents: 'none',
|
|
197
|
+
},
|
|
195
198
|
}));
|
|
196
199
|
export default SegmentedControl;
|
|
@@ -5,9 +5,10 @@ import { Platform, Pressable, View } from 'react-native';
|
|
|
5
5
|
import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated';
|
|
6
6
|
import { StyleSheet } from 'react-native-unistyles';
|
|
7
7
|
import { BodyText } from '../BodyText';
|
|
8
|
+
import { Icon } from '../Icon';
|
|
8
9
|
import { useSegmentedControlContext } from './SegmentedControl.context';
|
|
9
10
|
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
10
|
-
const SegmentedControlOptionRoot = ({ value, children, accessibilityLabel, disabled = false, style, states = {}, ...props }) => {
|
|
11
|
+
const SegmentedControlOptionRoot = ({ value, children, icon, accessibilityLabel, disabled = false, style, states = {}, ...props }) => {
|
|
11
12
|
const { value: selectedValue, select, disabled: allDisabled, size, registerOptionLayout, } = useSegmentedControlContext();
|
|
12
13
|
const { active = false } = states;
|
|
13
14
|
const reducedMotion = useReducedMotion();
|
|
@@ -35,7 +36,7 @@ const SegmentedControlOptionRoot = ({ value, children, accessibilityLabel, disab
|
|
|
35
36
|
const accessibleLabel = typeof children === 'string' || typeof children === 'number' ? String(children) : value;
|
|
36
37
|
return (_jsx(Pressable, { ...props, accessibilityRole: "radio", accessibilityState: { checked: selected, disabled: isDisabled }, accessibilityLabel: accessibilityLabel ?? accessibleLabel, onPress: onPress, onLayout: e => registerOptionLayout(value, e.nativeEvent.layout), disabled: isDisabled, style: [styles.option, style], ...(Platform.OS === 'web'
|
|
37
38
|
? { 'aria-label': accessibilityLabel ?? accessibleLabel }
|
|
38
|
-
: null), children: _jsxs(View, { style: styles.
|
|
39
|
+
: null), children: _jsxs(View, { style: styles.contentWrap, accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: [icon ? _jsx(Icon, { as: icon, size: "sm", style: styles.icon }) : null, _jsxs(View, { style: styles.labelWrap, children: [_jsx(BodyText, { size: "md", weight: "semibold", style: styles.labelSizer, accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: children }), _jsx(AnimatedView, { style: [styles.textLayer, styles.pointerEventsNone, regularLabelStyle], accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: _jsx(BodyText, { size: "md", weight: "regular", style: styles.textRegular, children: children }) }), _jsx(AnimatedView, { style: [styles.textLayer, styles.pointerEventsNone, selectedLabelStyle], accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", ...(Platform.OS === 'web' ? { 'aria-hidden': true } : null), children: _jsx(BodyText, { size: "md", weight: "semibold", style: styles.textSelected, children: children }) })] })] }) }));
|
|
39
40
|
};
|
|
40
41
|
const SegmentedControlOption = createPressable({ Root: SegmentedControlOptionRoot });
|
|
41
42
|
SegmentedControlOption.displayName = 'SegmentedControlOption';
|
|
@@ -90,6 +91,12 @@ const styles = StyleSheet.create(theme => ({
|
|
|
90
91
|
},
|
|
91
92
|
},
|
|
92
93
|
},
|
|
94
|
+
contentWrap: {
|
|
95
|
+
flexDirection: 'row',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
gap: theme.components.segmentedControl.gap,
|
|
99
|
+
},
|
|
93
100
|
labelWrap: {
|
|
94
101
|
position: 'relative',
|
|
95
102
|
alignItems: 'center',
|
|
@@ -105,6 +112,21 @@ const styles = StyleSheet.create(theme => ({
|
|
|
105
112
|
alignItems: 'center',
|
|
106
113
|
justifyContent: 'center',
|
|
107
114
|
},
|
|
115
|
+
pointerEventsNone: {
|
|
116
|
+
pointerEvents: 'none',
|
|
117
|
+
},
|
|
118
|
+
icon: {
|
|
119
|
+
variants: {
|
|
120
|
+
selected: {
|
|
121
|
+
true: {
|
|
122
|
+
color: theme.color.icon.inverted,
|
|
123
|
+
},
|
|
124
|
+
false: {
|
|
125
|
+
color: theme.color.icon.primary,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
108
130
|
textRegular: {
|
|
109
131
|
color: theme.color.text.primary,
|
|
110
132
|
},
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
2
|
import type { PressableProps, ViewProps } from 'react-native';
|
|
3
3
|
export interface SegmentedControlOptionProps extends Omit<PressableProps, 'children'> {
|
|
4
4
|
/** Unique option value. */
|
|
5
5
|
value: string;
|
|
6
6
|
/** Option label/content. */
|
|
7
7
|
children: ReactNode;
|
|
8
|
+
/** Optional leading icon. */
|
|
9
|
+
icon?: ComponentType<any>;
|
|
8
10
|
/** Disables only this option. */
|
|
9
11
|
disabled?: boolean;
|
|
10
12
|
style?: ViewProps['style'];
|
|
@@ -26,7 +26,12 @@ const TimePickerWheel = ({ value, setValue = () => { }, items }) => {
|
|
|
26
26
|
setValue(item.value);
|
|
27
27
|
}
|
|
28
28
|
}, [setValue, value]);
|
|
29
|
-
const renderOverlay = useCallback(() => (_jsxs(View, { style: [styles.overlayContainer
|
|
29
|
+
const renderOverlay = useCallback(() => (_jsxs(View, { style: [styles.overlayContainer, styles.pointerEventsNone], children: [_jsx(View, { style: [styles.fadeOverlay, styles.pointerEventsNone, { height: fadeHeight }], children: _jsxs(Svg, { width: "100%", height: "100%", preserveAspectRatio: "none", children: [_jsx(Defs, { children: _jsxs(LinearGradient, { id: `${gradientId}-top`, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx(Stop, { offset: "0", stopColor: theme.color.background.secondary, stopOpacity: 1 }), _jsx(Stop, { offset: "1", stopColor: theme.color.background.secondary, stopOpacity: 0 })] }) }), _jsx(Rect, { width: "100%", height: "100%", fill: `url(#${gradientId}-top)` })] }) }), _jsx(View, { style: [
|
|
30
|
+
styles.fadeOverlay,
|
|
31
|
+
styles.fadeOverlayBottom,
|
|
32
|
+
styles.pointerEventsNone,
|
|
33
|
+
{ height: fadeHeight },
|
|
34
|
+
], children: _jsxs(Svg, { width: "100%", height: "100%", preserveAspectRatio: "none", children: [_jsx(Defs, { children: _jsxs(LinearGradient, { id: `${gradientId}-bottom`, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx(Stop, { offset: "0", stopColor: theme.color.background.secondary, stopOpacity: 0 }), _jsx(Stop, { offset: "1", stopColor: theme.color.background.secondary, stopOpacity: 1 })] }) }), _jsx(Rect, { width: "100%", height: "100%", fill: `url(#${gradientId}-bottom)` })] }) })] })), [fadeHeight, gradientId, theme.color.background.secondary]);
|
|
30
35
|
const renderItem = useCallback(({ item }) => (_jsx(View, { style: styles.indicator, children: _jsx(BodyText, { size: "lg", children: item.label }) })), []);
|
|
31
36
|
return (_jsxs(View, { style: [styles.container, { height: pickerHeight }], children: [_jsx(View, { style: styles.overlayContainer, children: _jsx(View, { style: [styles.selection] }) }), _jsx(WheelPicker, { data: data, value: value, onValueChanged: handleValueChanged, itemHeight: ITEM_HEIGHT, visibleItemCount: displayCount, width: theme.components.timePicker.time.item.width, renderItem: renderItem, renderOverlay: renderOverlay })] }));
|
|
32
37
|
};
|
|
@@ -74,5 +79,8 @@ const styles = StyleSheet.create(theme => ({
|
|
|
74
79
|
top: undefined,
|
|
75
80
|
bottom: 0,
|
|
76
81
|
},
|
|
82
|
+
pointerEventsNone: {
|
|
83
|
+
pointerEvents: 'none',
|
|
84
|
+
},
|
|
77
85
|
}));
|
|
78
86
|
export default TimePickerWheel;
|
|
@@ -63,7 +63,7 @@ export const ToastProvider = ({ children, safeAreaPadding = true, }) => {
|
|
|
63
63
|
timers.current = {};
|
|
64
64
|
};
|
|
65
65
|
}, []);
|
|
66
|
-
return (_jsxs(ToastContext.Provider, { value: { addToast, removeToast }, children: [children, _jsx(View, {
|
|
66
|
+
return (_jsxs(ToastContext.Provider, { value: { addToast, removeToast }, children: [children, _jsx(View, { style: styles.container, children: _jsx(View, { style: styles.stack, children: toasts.map(t => (_jsx(ToastItem, { ref: el => {
|
|
67
67
|
toastRefs.current[t.id] = el;
|
|
68
68
|
}, toast: t, onClose: removeToast }, t.id))) }) })] }));
|
|
69
69
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import { TextInput, View } from 'react-native';
|
|
4
4
|
import { StyleSheet } from 'react-native-unistyles';
|
|
5
5
|
import { FormField } from '../FormField';
|
|
6
|
+
import { getNextIndexFromValueChange } from './VerificationInput.utils';
|
|
6
7
|
import { VerificationInputSlot } from './VerificationInputSlot';
|
|
7
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
9
|
const length = 6;
|
|
@@ -26,12 +27,12 @@ const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVa
|
|
|
26
27
|
setSelection(nextSelection);
|
|
27
28
|
}
|
|
28
29
|
}, [length, value]);
|
|
29
|
-
const updateValue = (nextValue) => {
|
|
30
|
+
const updateValue = useCallback((nextValue) => {
|
|
30
31
|
const trimmedValue = nextValue.slice(0, length);
|
|
31
32
|
latestValueRef.current = trimmedValue;
|
|
32
33
|
setDisplayValue(trimmedValue);
|
|
33
34
|
onChangeText?.(trimmedValue);
|
|
34
|
-
};
|
|
35
|
+
}, [length, onChangeText]);
|
|
35
36
|
const setSelectionIndex = (index) => {
|
|
36
37
|
const clampedIndex = Math.max(0, Math.min(index, length));
|
|
37
38
|
const hasChar = !!latestValueRef.current[clampedIndex];
|
|
@@ -50,15 +51,6 @@ const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVa
|
|
|
50
51
|
setSelection(nextSelection);
|
|
51
52
|
setFocusedIndex(Math.min(clampedIndex, length - 1));
|
|
52
53
|
};
|
|
53
|
-
const findDiffIndex = (prevValue, nextValue) => {
|
|
54
|
-
const minLength = Math.min(prevValue.length, nextValue.length);
|
|
55
|
-
for (let i = 0; i < minLength; i += 1) {
|
|
56
|
-
if (prevValue[i] !== nextValue[i]) {
|
|
57
|
-
return i;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return minLength;
|
|
61
|
-
};
|
|
62
54
|
const handleChangeText = (text) => {
|
|
63
55
|
const prevValue = latestValueRef.current;
|
|
64
56
|
const nextValue = text.slice(0, length);
|
|
@@ -67,11 +59,7 @@ const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVa
|
|
|
67
59
|
const diff = nextLength - prevLength;
|
|
68
60
|
const isBulkInsert = text.length > 1 && diff > 1;
|
|
69
61
|
const shouldBlur = nextLength >= length;
|
|
70
|
-
|
|
71
|
-
if (Platform.OS === 'android') {
|
|
72
|
-
const editedIndex = findDiffIndex(prevValue, nextValue);
|
|
73
|
-
nextIndex = diff >= 0 ? Math.min(editedIndex + 1, length) : Math.max(editedIndex, 0);
|
|
74
|
-
}
|
|
62
|
+
const nextIndex = getNextIndexFromValueChange({ prevValue, nextValue, length });
|
|
75
63
|
updateValue(nextValue);
|
|
76
64
|
if (isBulkInsert) {
|
|
77
65
|
setCaretIndex(Math.min(nextLength, length));
|
|
@@ -135,7 +123,7 @@ const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVa
|
|
|
135
123
|
setSelectionIndex(index);
|
|
136
124
|
}
|
|
137
125
|
},
|
|
138
|
-
}), [length,
|
|
126
|
+
}), [length, updateValue]);
|
|
139
127
|
const slots = Array.from({ length }, (_, index) => index);
|
|
140
128
|
const getAccessibilityLabel = () => {
|
|
141
129
|
return label || props.accessibilityLabel;
|
|
@@ -172,7 +160,7 @@ const VerificationInput = forwardRef(({ value = '', onChangeText, label, labelVa
|
|
|
172
160
|
latestSelectionRef.current = nextSelection;
|
|
173
161
|
setSelection(nextSelection);
|
|
174
162
|
setFocusedIndex(Math.min(nextSelection.start, length - 1));
|
|
175
|
-
}, onFocus: handleFocus, onBlur: handleBlur, selection: selection, keyboardType: "number-pad", textContentType: "oneTimeCode", autoComplete: "sms-otp", secureTextEntry: secureTextEntry, maxLength: length, caretHidden: true, style: styles.hiddenInput
|
|
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 => {
|
|
176
164
|
const char = displayValue[index] || '';
|
|
177
165
|
const isActive = focusedIndex === index;
|
|
178
166
|
const displayChar = secureTextEntry && char ? '*' : char;
|
|
@@ -199,11 +187,12 @@ const styles = StyleSheet.create(theme => ({
|
|
|
199
187
|
position: 'absolute',
|
|
200
188
|
width: '100%',
|
|
201
189
|
height: '100%',
|
|
190
|
+
pointerEvents: 'none',
|
|
202
191
|
left: 0,
|
|
203
192
|
top: 0,
|
|
204
193
|
color: 'transparent',
|
|
205
194
|
fontSize: 1,
|
|
206
|
-
opacity: 0.
|
|
195
|
+
opacity: 0.01,
|
|
207
196
|
},
|
|
208
197
|
}));
|
|
209
198
|
VerificationInput.displayName = 'VerificationInput';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface GetNextIndexFromValueChangeOptions {
|
|
2
|
+
prevValue: string;
|
|
3
|
+
nextValue: string;
|
|
4
|
+
length: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const findDiffIndex: (prevValue: string, nextValue: string) => number;
|
|
7
|
+
export declare const getNextIndexFromValueChange: ({ prevValue, nextValue, length, }: GetNextIndexFromValueChangeOptions) => number;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const findDiffIndex = (prevValue, nextValue) => {
|
|
2
|
+
const minLength = Math.min(prevValue.length, nextValue.length);
|
|
3
|
+
for (let i = 0; i < minLength; i += 1) {
|
|
4
|
+
if (prevValue[i] !== nextValue[i]) {
|
|
5
|
+
return i;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return minLength;
|
|
9
|
+
};
|
|
10
|
+
export const getNextIndexFromValueChange = ({ prevValue, nextValue, length, }) => {
|
|
11
|
+
const diff = nextValue.length - prevValue.length;
|
|
12
|
+
const editedIndex = findDiffIndex(prevValue, nextValue);
|
|
13
|
+
if (diff >= 0) {
|
|
14
|
+
return Math.min(editedIndex + 1, length);
|
|
15
|
+
}
|
|
16
|
+
return Math.max(editedIndex, 0);
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { findDiffIndex, getNextIndexFromValueChange } from './VerificationInput.utils';
|
|
3
|
+
describe('findDiffIndex', () => {
|
|
4
|
+
it('returns first differing index', () => {
|
|
5
|
+
expect(findDiffIndex('12', '19')).toBe(1);
|
|
6
|
+
});
|
|
7
|
+
it('returns previous length when next appends', () => {
|
|
8
|
+
expect(findDiffIndex('12', '123')).toBe(2);
|
|
9
|
+
});
|
|
10
|
+
it('returns next length when next shortens with same prefix', () => {
|
|
11
|
+
expect(findDiffIndex('123', '12')).toBe(2);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('getNextIndexFromValueChange', () => {
|
|
15
|
+
it('moves to the slot after the one that changed when inserting in an empty later slot', () => {
|
|
16
|
+
expect(getNextIndexFromValueChange({
|
|
17
|
+
prevValue: '12',
|
|
18
|
+
nextValue: '123',
|
|
19
|
+
length: 6,
|
|
20
|
+
})).toBe(3);
|
|
21
|
+
});
|
|
22
|
+
it('caps at length when value becomes full', () => {
|
|
23
|
+
expect(getNextIndexFromValueChange({
|
|
24
|
+
prevValue: '12345',
|
|
25
|
+
nextValue: '123456',
|
|
26
|
+
length: 6,
|
|
27
|
+
})).toBe(6);
|
|
28
|
+
});
|
|
29
|
+
it('stays at edited index for deletions', () => {
|
|
30
|
+
expect(getNextIndexFromValueChange({
|
|
31
|
+
prevValue: '123',
|
|
32
|
+
nextValue: '12',
|
|
33
|
+
length: 6,
|
|
34
|
+
})).toBe(2);
|
|
35
|
+
});
|
|
36
|
+
});
|
package/docs/changelog.mdx
CHANGED
|
@@ -9,6 +9,119 @@ 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.27.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [#990](https://github.com/utilitywarehouse/hearth/pull/990) [`958e0e1`](https://github.com/utilitywarehouse/hearth/commit/958e0e1a9d5451d1e11fecadc69ae3c5ad9d42ca) Thanks [@declanelcocks](https://github.com/declanelcocks)! - 🐛 [FIX]: Fix `Modal` layout when `inNavModal` and `stickyFooter={false}`.
|
|
17
|
+
|
|
18
|
+
Corrects the container flex style for `inNavModal` modals with a non-sticky footer, where the UX was not great when scrolling.
|
|
19
|
+
|
|
20
|
+
**Components affected**:
|
|
21
|
+
- `Modal`
|
|
22
|
+
|
|
23
|
+
**Developer changes**:
|
|
24
|
+
|
|
25
|
+
No changes required.
|
|
26
|
+
|
|
27
|
+
- [#992](https://github.com/utilitywarehouse/hearth/pull/992) [`2560b3d`](https://github.com/utilitywarehouse/hearth/commit/2560b3dcba7ed4981fad585628f96afd07d8de4f) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Add optional leading `icon` support to `SegmentedControlOption`.
|
|
28
|
+
|
|
29
|
+
This adds an optional `icon` prop to `SegmentedControlOption`, allowing icons to be displayed before option labels in segmented controls.
|
|
30
|
+
|
|
31
|
+
Docs and stories were updated to include icon usage examples.
|
|
32
|
+
|
|
33
|
+
**Components affected**:
|
|
34
|
+
- `SegmentedControlOption`
|
|
35
|
+
|
|
36
|
+
**Developer changes**:
|
|
37
|
+
|
|
38
|
+
No changes required for existing usage.
|
|
39
|
+
|
|
40
|
+
To use the new optional icon prop:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { SegmentedControl, SegmentedControlOption } from '@utilitywarehouse/hearth-react-native';
|
|
44
|
+
import { ElectricitySmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
45
|
+
|
|
46
|
+
<SegmentedControl defaultValue="energy">
|
|
47
|
+
<SegmentedControlOption value="energy" icon={ElectricitySmallIcon}>
|
|
48
|
+
Energy
|
|
49
|
+
</SegmentedControlOption>
|
|
50
|
+
<SegmentedControlOption value="broadband">Broadband</SegmentedControlOption>
|
|
51
|
+
</SegmentedControl>;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 0.27.0
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- [#987](https://github.com/utilitywarehouse/hearth/pull/987) [`eb962d2`](https://github.com/utilitywarehouse/hearth/commit/eb962d2f33b63fa3aeda0b291fd41ace90d04c41) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `SegmentedControl` and `SegmentedControlOption` components.
|
|
59
|
+
|
|
60
|
+
This introduces a new segmented control component for switching between a small set of related options.
|
|
61
|
+
The component includes controlled and uncontrolled usage, size variants (`sm`, `md`), animated selected indicator movement, and improved accessibility semantics for screen readers.
|
|
62
|
+
|
|
63
|
+
**Components affected**:
|
|
64
|
+
- `SegmentedControl`
|
|
65
|
+
- `SegmentedControlOption`
|
|
66
|
+
|
|
67
|
+
**Developer changes**:
|
|
68
|
+
|
|
69
|
+
Import and compose the new components as follows:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { SegmentedControl, SegmentedControlOption } from '@utilitywarehouse/hearth-react-native';
|
|
73
|
+
|
|
74
|
+
<SegmentedControl defaultValue="day" size="sm">
|
|
75
|
+
<SegmentedControlOption value="day">Day</SegmentedControlOption>
|
|
76
|
+
<SegmentedControlOption value="week">Week</SegmentedControlOption>
|
|
77
|
+
<SegmentedControlOption value="month">Month</SegmentedControlOption>
|
|
78
|
+
</SegmentedControl>;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- [#989](https://github.com/utilitywarehouse/hearth/pull/989) [`c97122e`](https://github.com/utilitywarehouse/hearth/commit/c97122eb429ec4adef656fb245a9256a5619df61) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Ensure horizontal `Banner` fills available width when `onPress` is not provided.
|
|
84
|
+
|
|
85
|
+
Fixed a layout issue where a horizontal `Banner` without `onPress` could fail to stretch correctly within its parent container.
|
|
86
|
+
|
|
87
|
+
**Components affected**:
|
|
88
|
+
- `Banner`
|
|
89
|
+
|
|
90
|
+
**Developer changes**:
|
|
91
|
+
|
|
92
|
+
No changes required.
|
|
93
|
+
|
|
94
|
+
## 0.26.0
|
|
95
|
+
|
|
96
|
+
### Minor Changes
|
|
97
|
+
|
|
98
|
+
- [#981](https://github.com/utilitywarehouse/hearth/pull/981) [`df56387`](https://github.com/utilitywarehouse/hearth/commit/df563872e6bf040d419f6c7fce2343ebe560edb9) Thanks [@declanelcocks](https://github.com/declanelcocks)! - 🌟 [ENHANCEMENT]: Improve `Modal` behaviour when used inside a React Navigation modal (`inNavModal`).
|
|
99
|
+
|
|
100
|
+
The following improvements have been made to the `Modal` component when used in a navigation context with `inNavModal={true}`:
|
|
101
|
+
- **`scrollable` prop**: Content is now rendered inside a `ScrollView` by default. Set `scrollable={false}` to opt out, for example when you need to centre content or use a custom layout.
|
|
102
|
+
- **`stickyFooter` support**: The `stickyFooter` prop now works correctly in `inNavModal` mode.
|
|
103
|
+
- **Auto full-screen detection**: When the modal fills the entire screen (e.g. with `presentation: 'fullScreenModal'`), the `fullscreen` style is applied automatically. The `fullscreen` prop is no longer available when `inNavModal` is `true`.
|
|
104
|
+
|
|
105
|
+
**Components affected**:
|
|
106
|
+
- `Modal`
|
|
107
|
+
|
|
108
|
+
**Developer changes**:
|
|
109
|
+
|
|
110
|
+
No changes are required for existing usage. If you were passing `fullscreen` alongside `inNavModal={true}`, remove the `fullscreen` prop — full-screen styling is now detected automatically:
|
|
111
|
+
|
|
112
|
+
```diff
|
|
113
|
+
- <Modal inNavModal fullscreen>
|
|
114
|
+
+ <Modal inNavModal>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
To disable the default `ScrollView` wrapping in `inNavModal` mode:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<Modal inNavModal scrollable={false}>
|
|
121
|
+
{/* custom layout */}
|
|
122
|
+
</Modal>
|
|
123
|
+
```
|
|
124
|
+
|
|
12
125
|
## 0.25.0
|
|
13
126
|
|
|
14
127
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilitywarehouse/hearth-react-native",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2-test",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"vite": "^7.1.3",
|
|
57
57
|
"vite-plugin-svgr": "^4.5.0",
|
|
58
58
|
"vitest": "^3.2.4",
|
|
59
|
-
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
60
|
-
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
61
59
|
"@utilitywarehouse/hearth-svg-assets": "^0.5.0",
|
|
62
|
-
"@utilitywarehouse/hearth-
|
|
63
|
-
"@utilitywarehouse/hearth-react-icons": "^0.8.0"
|
|
60
|
+
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
61
|
+
"@utilitywarehouse/hearth-react-icons": "^0.8.0",
|
|
62
|
+
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
63
|
+
"@utilitywarehouse/hearth-tokens": "^0.2.3"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"figma:create": "figma connect create",
|
|
86
86
|
"figma:publish": "figma connect publish",
|
|
87
87
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
88
|
+
"test:storybook": "vitest run --project storybook",
|
|
88
89
|
"dev": "npm run copyChangelog && storybook dev -p 6006",
|
|
89
90
|
"dev:docs": "storybook dev -p 6002 --no-open --docs",
|
|
90
91
|
"build:storybook": "npm run copyChangelog && storybook build",
|
|
@@ -328,13 +328,17 @@ const Carousel = ({
|
|
|
328
328
|
onScrollEndDrag={handleWebScrollEnd}
|
|
329
329
|
ref={scrollViewRef as any}
|
|
330
330
|
scrollEnabled={!disabled}
|
|
331
|
-
pointerEvents={disabled ? 'none' : 'auto'}
|
|
332
331
|
scrollEventThrottle={16}
|
|
333
332
|
showsHorizontalScrollIndicator={false}
|
|
334
333
|
snapToInterval={itemWidth || width}
|
|
335
334
|
snapToAlignment={centered ? 'center' : 'start'}
|
|
336
335
|
decelerationRate="fast"
|
|
337
|
-
style={[
|
|
336
|
+
style={[
|
|
337
|
+
styles.webContainer,
|
|
338
|
+
webContainerStyles,
|
|
339
|
+
itemsStyle,
|
|
340
|
+
{ pointerEvents: disabled ? 'none' : 'auto' },
|
|
341
|
+
]}
|
|
338
342
|
contentContainerStyle={[styles.webContentContainer, webContentContainerStyle]}
|
|
339
343
|
{...props}
|
|
340
344
|
>
|