@utilitywarehouse/hearth-react-native 0.27.1 → 0.27.2-testid-fix-1
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 +15 -0
- package/build/components/Button/ButtonRoot.js +8 -0
- package/build/components/Carousel/Carousel.js +6 -1
- 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/List/List.js +2 -2
- package/build/components/Modal/Modal.js +16 -11
- package/build/components/SegmentedControl/SegmentedControl.js +4 -1
- package/build/components/SegmentedControl/SegmentedControlOption.js +4 -1
- package/build/components/TimePicker/TimePickerWheel.js +9 -1
- package/build/components/Toast/Toast.context.js +1 -1
- package/build/components/VerificationInput/VerificationInput.js +11 -22
- 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 +5 -4
- package/src/components/Button/Button.stories.tsx +43 -7
- package/src/components/Button/ButtonRoot.tsx +8 -0
- 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 +31 -16
- package/src/components/SegmentedControl/SegmentedControl.tsx +4 -1
- package/src/components/SegmentedControl/SegmentedControlOption.tsx +5 -4
- 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 +18 -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilitywarehouse/hearth-react-native",
|
|
3
|
-
"version": "0.27.1",
|
|
3
|
+
"version": "0.27.2-testid-fix-1",
|
|
4
4
|
"description": "Utility Warehouse React Native UI library",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"vite": "^7.1.3",
|
|
57
57
|
"vite-plugin-svgr": "^4.5.0",
|
|
58
58
|
"vitest": "^3.2.4",
|
|
59
|
-
"@utilitywarehouse/hearth-react-icons": "^0.8.0",
|
|
60
|
-
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
61
59
|
"@utilitywarehouse/hearth-react-native-icons": "^0.8.0",
|
|
62
|
-
"@utilitywarehouse/hearth-tokens": "^0.2.
|
|
60
|
+
"@utilitywarehouse/hearth-tokens": "^0.2.4",
|
|
61
|
+
"@utilitywarehouse/hearth-fonts": "^0.0.4",
|
|
62
|
+
"@utilitywarehouse/hearth-react-icons": "^0.8.0",
|
|
63
63
|
"@utilitywarehouse/hearth-svg-assets": "^0.5.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
@@ -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",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-native';
|
|
2
2
|
import * as Icons from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
3
|
import { AddSmallIcon, ChevronRightSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
4
4
|
import { Platform } from 'react-native';
|
|
@@ -92,7 +92,7 @@ type Story = StoryObj<typeof meta>;
|
|
|
92
92
|
|
|
93
93
|
export const Playground: Story = {
|
|
94
94
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
95
|
-
render: ({ icon: _icon, children: _, ...args }) => {
|
|
95
|
+
render: ({ icon: _icon, children: _, ...args }: StoryObj<typeof meta.args>) => {
|
|
96
96
|
// @ts-expect-error - This is a playground
|
|
97
97
|
const icon = _icon === 'none' ? undefined : Icons[_icon];
|
|
98
98
|
return <Button {...args} icon={icon} />;
|
|
@@ -104,7 +104,7 @@ export const Variants: Story = {
|
|
|
104
104
|
controls: { exclude: ['variant'] },
|
|
105
105
|
},
|
|
106
106
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
107
|
-
render: ({ icon: _icon, children: _, ...args }) => {
|
|
107
|
+
render: ({ icon: _icon, children: _, ...args }: StoryObj<typeof meta.args>) => {
|
|
108
108
|
// @ts-expect-error - This is a playground
|
|
109
109
|
const icon = _icon === 'none' ? undefined : Icons[_icon];
|
|
110
110
|
return (
|
|
@@ -123,11 +123,9 @@ export const Variants: Story = {
|
|
|
123
123
|
{args.colorScheme !== 'highlight' && (
|
|
124
124
|
<>
|
|
125
125
|
<VariantTitle title="Outline" invert={args.inverted}>
|
|
126
|
-
{/* @ts-expect-error - story loop types don't match */}
|
|
127
126
|
<Button {...args} variant="outline" icon={icon} />
|
|
128
127
|
</VariantTitle>
|
|
129
128
|
<VariantTitle title="Ghost" invert={args.inverted}>
|
|
130
|
-
{/* @ts-expect-error - story loop types don't match */}
|
|
131
129
|
<Button {...args} variant="ghost" icon={icon} />
|
|
132
130
|
</VariantTitle>
|
|
133
131
|
</>
|
|
@@ -138,6 +136,44 @@ export const Variants: Story = {
|
|
|
138
136
|
},
|
|
139
137
|
};
|
|
140
138
|
|
|
139
|
+
export const PaddingNone: Story = {
|
|
140
|
+
parameters: {
|
|
141
|
+
controls: {
|
|
142
|
+
include: ['text', 'size', 'inverted', 'icon', 'iconPosition'],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
|
146
|
+
render: ({ icon: _icon, children: _, ...args }: StoryObj<typeof meta.args>) => {
|
|
147
|
+
// @ts-expect-error - This is a playground
|
|
148
|
+
const icon = _icon === 'none' ? undefined : Icons[_icon];
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Flex direction="column" spacing="lg">
|
|
152
|
+
<VariantTitle title="Default Padding" invert={args.inverted}>
|
|
153
|
+
<Flex direction="row" align="center" spacing="none">
|
|
154
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
155
|
+
<Button
|
|
156
|
+
{...args}
|
|
157
|
+
colorScheme="functional"
|
|
158
|
+
variant="ghost"
|
|
159
|
+
icon={icon}
|
|
160
|
+
paddingNone={false}
|
|
161
|
+
/>
|
|
162
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
163
|
+
</Flex>
|
|
164
|
+
</VariantTitle>
|
|
165
|
+
<VariantTitle title="No Padding (paddingNone)" invert={args.inverted}>
|
|
166
|
+
<Flex direction="row" align="center" spacing="none">
|
|
167
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
168
|
+
<Button {...args} colorScheme="functional" variant="ghost" icon={icon} paddingNone />
|
|
169
|
+
<Box backgroundColor="brand" width="100" height="100" />
|
|
170
|
+
</Flex>
|
|
171
|
+
</VariantTitle>
|
|
172
|
+
</Flex>
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
141
177
|
type ColorScheme = ButtonProps['colorScheme'];
|
|
142
178
|
type Variant = ButtonProps['variant'];
|
|
143
179
|
|
|
@@ -145,7 +181,7 @@ export const KitchenSink: Story = {
|
|
|
145
181
|
parameters: {
|
|
146
182
|
controls: { include: ['text', 'size', 'inverted'] },
|
|
147
183
|
},
|
|
148
|
-
render: ({ text, inverted, size }) => {
|
|
184
|
+
render: ({ text, inverted, size }: StoryObj<typeof meta.args>) => {
|
|
149
185
|
const schemes: Array<ColorScheme> = ['highlight', 'destructive', 'affirmative', 'functional'];
|
|
150
186
|
const variants: Array<Variant> = ['emphasis', 'solid', 'outline', 'ghost'];
|
|
151
187
|
return (
|
|
@@ -174,7 +210,7 @@ export const KitchenSink: Story = {
|
|
|
174
210
|
.map(variant => (
|
|
175
211
|
<Box key={variant} mb="100">
|
|
176
212
|
<Box mb="100">
|
|
177
|
-
<DetailText size="lg"
|
|
213
|
+
<DetailText size="lg" inverted={inverted}>
|
|
178
214
|
{scheme} - {variant}
|
|
179
215
|
</DetailText>
|
|
180
216
|
</Box>
|
|
@@ -132,6 +132,14 @@ const styles = StyleSheet.create(theme => ({
|
|
|
132
132
|
paddingHorizontal: 0,
|
|
133
133
|
},
|
|
134
134
|
},
|
|
135
|
+
{
|
|
136
|
+
size: 'md',
|
|
137
|
+
paddingNone: true,
|
|
138
|
+
variant: 'ghost',
|
|
139
|
+
styles: {
|
|
140
|
+
paddingHorizontal: 0,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
135
143
|
// Variant Color Schemes
|
|
136
144
|
// Emphasis
|
|
137
145
|
// Emphasis Yellow
|
|
@@ -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
|
>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-native';
|
|
2
2
|
import * as Icons from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
|
+
import { ComponentType } from 'react';
|
|
3
4
|
import { IconContainer } from '.';
|
|
4
5
|
import { VariantTitle } from '../../../docs/components';
|
|
5
6
|
import { Box } from '../Box';
|
|
@@ -53,7 +54,9 @@ export default meta;
|
|
|
53
54
|
|
|
54
55
|
type Story = StoryObj<typeof meta>;
|
|
55
56
|
|
|
56
|
-
export const Playground: Story = {
|
|
57
|
+
export const Playground: Story = {
|
|
58
|
+
render: (args: typeof meta.args) => <IconContainer {...args} />,
|
|
59
|
+
};
|
|
57
60
|
|
|
58
61
|
export const Subtle: Story = {
|
|
59
62
|
parameters: {
|
|
@@ -86,7 +89,7 @@ export const KitchenSink: Story = {
|
|
|
86
89
|
parameters: {
|
|
87
90
|
controls: { exclude: ['radiusNone', 'variant', 'color', 'size'] },
|
|
88
91
|
},
|
|
89
|
-
render: ({ icon }) => {
|
|
92
|
+
render: ({ icon }: { icon: ComponentType }) => {
|
|
90
93
|
const sizes: Array<'sm' | 'md' | 'lg'> = ['sm', 'md', 'lg'];
|
|
91
94
|
const colors: Array<
|
|
92
95
|
'pig' | 'energy' | 'broadband' | 'mobile' | 'insurance' | 'cashback' | 'highlight'
|
|
@@ -95,33 +98,35 @@ export const KitchenSink: Story = {
|
|
|
95
98
|
<Flex direction="column" spacing="lg">
|
|
96
99
|
{sizes.map(size => (
|
|
97
100
|
<Box key={size} gap="300">
|
|
98
|
-
<VariantTitle title={`Size: ${size.toUpperCase()} / Subtle`}>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
color
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
101
|
+
<VariantTitle title={`Size: ${size.toUpperCase()} / Subtle`}>
|
|
102
|
+
<Flex direction="row" wrap="wrap" spacing="md">
|
|
103
|
+
{colors.map(color => (
|
|
104
|
+
<IconContainer
|
|
105
|
+
key={`${size}-subtle-${color}`}
|
|
106
|
+
icon={icon}
|
|
107
|
+
size={size}
|
|
108
|
+
variant="subtle"
|
|
109
|
+
color={color}
|
|
110
|
+
/>
|
|
111
|
+
))}
|
|
112
|
+
</Flex>
|
|
113
|
+
</VariantTitle>
|
|
114
|
+
<VariantTitle title={`Size: ${size.toUpperCase()} / Emphasis`}>
|
|
115
|
+
<Flex direction="row" wrap="wrap" spacing="md">
|
|
116
|
+
{colors.map(
|
|
117
|
+
color =>
|
|
118
|
+
color !== 'highlight' && (
|
|
119
|
+
<IconContainer
|
|
120
|
+
key={`${size}-emphasis-${color}`}
|
|
121
|
+
icon={icon}
|
|
122
|
+
size={size}
|
|
123
|
+
variant="emphasis"
|
|
124
|
+
color={color}
|
|
125
|
+
/>
|
|
126
|
+
)
|
|
127
|
+
)}
|
|
128
|
+
</Flex>
|
|
129
|
+
</VariantTitle>
|
|
125
130
|
</Box>
|
|
126
131
|
))}
|
|
127
132
|
</Flex>
|
|
@@ -14,7 +14,8 @@ const List = ({
|
|
|
14
14
|
invalidText,
|
|
15
15
|
...props
|
|
16
16
|
}: ListProps) => {
|
|
17
|
-
const { loading, disabled, container = 'none' } = props;
|
|
17
|
+
const { loading, disabled, container = 'none', testID, style, ...rest } = props;
|
|
18
|
+
|
|
18
19
|
const orderRef = useRef<string[]>([]);
|
|
19
20
|
const [firstItemId, setFirstItemId] = useState<string | undefined>(undefined);
|
|
20
21
|
const containerToCard: {
|
|
@@ -51,7 +52,7 @@ const List = ({
|
|
|
51
52
|
styles.useVariants({ disabled });
|
|
52
53
|
return (
|
|
53
54
|
<ListContext.Provider value={value}>
|
|
54
|
-
<View {...
|
|
55
|
+
<View {...rest} style={[styles.container, style]}>
|
|
55
56
|
{heading ? (
|
|
56
57
|
<SectionHeader
|
|
57
58
|
heading={heading}
|
|
@@ -61,10 +62,10 @@ const List = ({
|
|
|
61
62
|
/>
|
|
62
63
|
) : null}
|
|
63
64
|
{container === 'none' ? (
|
|
64
|
-
<View>{children}</View>
|
|
65
|
+
<View testID={testID}>{children}</View>
|
|
65
66
|
) : (
|
|
66
67
|
React.Children.count(children) > 0 && (
|
|
67
|
-
<Card {...containerToCard} noPadding style={styles.card}>
|
|
68
|
+
<Card {...containerToCard} noPadding style={styles.card} testID={testID}>
|
|
68
69
|
<>{children}</>
|
|
69
70
|
</Card>
|
|
70
71
|
)
|
|
@@ -7,7 +7,14 @@ import {
|
|
|
7
7
|
import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
|
|
8
8
|
import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
9
9
|
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AccessibilityInfo,
|
|
12
|
+
Dimensions,
|
|
13
|
+
Platform,
|
|
14
|
+
ScrollView,
|
|
15
|
+
View,
|
|
16
|
+
findNodeHandle,
|
|
17
|
+
} from 'react-native';
|
|
11
18
|
import Animated, {
|
|
12
19
|
Easing,
|
|
13
20
|
useAnimatedStyle,
|
|
@@ -292,13 +299,23 @@ const Modal = ({
|
|
|
292
299
|
</View>
|
|
293
300
|
) : null}
|
|
294
301
|
{inNavModal && (
|
|
295
|
-
<InNavModalContainer
|
|
302
|
+
<InNavModalContainer
|
|
303
|
+
style={{
|
|
304
|
+
flex: stickyFooter ? 1 : 0,
|
|
305
|
+
...(scrollable ? { marginHorizontal: -1 } : {}),
|
|
306
|
+
}}
|
|
307
|
+
{...(scrollable ? { contentContainerStyle: { paddingHorizontal: 1 } } : {})}
|
|
308
|
+
>
|
|
296
309
|
{children}
|
|
297
|
-
{!stickyFooter ?
|
|
310
|
+
{!stickyFooter ? (
|
|
311
|
+
<View style={styles.inNavModalFooterContainer}>{footer}</View>
|
|
312
|
+
) : null}
|
|
298
313
|
</InNavModalContainer>
|
|
299
314
|
)}
|
|
300
315
|
{!inNavModal && children}
|
|
301
|
-
{((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons
|
|
316
|
+
{((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons
|
|
317
|
+
? footer
|
|
318
|
+
: null}
|
|
302
319
|
</View>
|
|
303
320
|
)}
|
|
304
321
|
</>
|
|
@@ -322,7 +339,7 @@ const Modal = ({
|
|
|
322
339
|
|
|
323
340
|
return inNavModal ? (
|
|
324
341
|
<View
|
|
325
|
-
onLayout={
|
|
342
|
+
onLayout={e => {
|
|
326
343
|
setInNavModalHeight(e.nativeEvent.layout.height);
|
|
327
344
|
}}
|
|
328
345
|
style={{
|
|
@@ -338,9 +355,7 @@ const Modal = ({
|
|
|
338
355
|
<Animated.View
|
|
339
356
|
style={[styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle]}
|
|
340
357
|
>
|
|
341
|
-
<View style={styles.inNavModalContent}>
|
|
342
|
-
{content}
|
|
343
|
-
</View>
|
|
358
|
+
<View style={styles.inNavModalContent}>{content}</View>
|
|
344
359
|
</Animated.View>
|
|
345
360
|
</View>
|
|
346
361
|
) : (
|
|
@@ -471,7 +486,7 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
471
486
|
borderTopLeftRadius: theme.components.modal.borderRadius,
|
|
472
487
|
borderTopRightRadius: theme.components.modal.borderRadius,
|
|
473
488
|
backgroundColor: theme.color.surface.neutral.strong,
|
|
474
|
-
paddingBottom: theme.components.
|
|
489
|
+
paddingBottom: theme.components.bottomSheet.padding + rt.insets.bottom,
|
|
475
490
|
variants: {
|
|
476
491
|
background: {
|
|
477
492
|
primary: {},
|
|
@@ -481,22 +496,22 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
481
496
|
},
|
|
482
497
|
fullscreen: {
|
|
483
498
|
true: {
|
|
484
|
-
padding: theme.components.
|
|
499
|
+
padding: theme.components.bottomSheet.padding,
|
|
485
500
|
paddingTop: rt.insets.top,
|
|
486
501
|
},
|
|
487
502
|
false: {
|
|
488
|
-
padding: theme.components.
|
|
489
|
-
}
|
|
490
|
-
}
|
|
503
|
+
padding: theme.components.bottomSheet.padding,
|
|
504
|
+
},
|
|
505
|
+
},
|
|
491
506
|
},
|
|
492
507
|
},
|
|
493
508
|
inNavModalFooterContainer: {
|
|
494
|
-
paddingTop: theme.components.
|
|
509
|
+
paddingTop: theme.components.bottomSheet.padding,
|
|
495
510
|
},
|
|
496
511
|
androidContainer: {
|
|
497
512
|
height: rt.insets.top + 18,
|
|
498
|
-
paddingLeft: theme.components.
|
|
499
|
-
paddingRight: theme.components.
|
|
513
|
+
paddingLeft: theme.components.bottomSheet.padding,
|
|
514
|
+
paddingRight: theme.components.bottomSheet.padding,
|
|
500
515
|
justifyContent: 'flex-end',
|
|
501
516
|
},
|
|
502
517
|
pretendContent: {
|
|
@@ -206,7 +206,7 @@ const SegmentedControl = ({
|
|
|
206
206
|
{...remainingProps}
|
|
207
207
|
>
|
|
208
208
|
{hasIndicator ? (
|
|
209
|
-
<Indicator
|
|
209
|
+
<Indicator style={[styles.indicator, styles.pointerEventsNone, indicatorStyle]} />
|
|
210
210
|
) : null}
|
|
211
211
|
{children}
|
|
212
212
|
</View>
|
|
@@ -252,6 +252,9 @@ const styles = StyleSheet.create(theme => ({
|
|
|
252
252
|
borderRadius: theme.components.segmentedControl.borderRadius,
|
|
253
253
|
backgroundColor: theme.color.interactive.brand.surface.strong.default,
|
|
254
254
|
},
|
|
255
|
+
pointerEventsNone: {
|
|
256
|
+
pointerEvents: 'none',
|
|
257
|
+
},
|
|
255
258
|
}));
|
|
256
259
|
|
|
257
260
|
export default SegmentedControl;
|
|
@@ -101,8 +101,7 @@ const SegmentedControlOptionRoot = ({
|
|
|
101
101
|
{children}
|
|
102
102
|
</BodyText>
|
|
103
103
|
<AnimatedView
|
|
104
|
-
|
|
105
|
-
style={[styles.textLayer, regularLabelStyle]}
|
|
104
|
+
style={[styles.textLayer, styles.pointerEventsNone, regularLabelStyle]}
|
|
106
105
|
accessible={false}
|
|
107
106
|
accessibilityElementsHidden
|
|
108
107
|
importantForAccessibility="no-hide-descendants"
|
|
@@ -113,8 +112,7 @@ const SegmentedControlOptionRoot = ({
|
|
|
113
112
|
</BodyText>
|
|
114
113
|
</AnimatedView>
|
|
115
114
|
<AnimatedView
|
|
116
|
-
|
|
117
|
-
style={[styles.textLayer, selectedLabelStyle]}
|
|
115
|
+
style={[styles.textLayer, styles.pointerEventsNone, selectedLabelStyle]}
|
|
118
116
|
accessible={false}
|
|
119
117
|
accessibilityElementsHidden
|
|
120
118
|
importantForAccessibility="no-hide-descendants"
|
|
@@ -206,6 +204,9 @@ const styles = StyleSheet.create(theme => ({
|
|
|
206
204
|
alignItems: 'center',
|
|
207
205
|
justifyContent: 'center',
|
|
208
206
|
},
|
|
207
|
+
pointerEventsNone: {
|
|
208
|
+
pointerEvents: 'none',
|
|
209
|
+
},
|
|
209
210
|
icon: {
|
|
210
211
|
variants: {
|
|
211
212
|
selected: {
|
|
@@ -46,8 +46,8 @@ const TimePickerWheel = ({ value, setValue = () => {}, items }: TimePickerWheelP
|
|
|
46
46
|
|
|
47
47
|
const renderOverlay = useCallback(
|
|
48
48
|
() => (
|
|
49
|
-
<View style={[styles.overlayContainer]}
|
|
50
|
-
<View
|
|
49
|
+
<View style={[styles.overlayContainer, styles.pointerEventsNone]}>
|
|
50
|
+
<View style={[styles.fadeOverlay, styles.pointerEventsNone, { height: fadeHeight }]}>
|
|
51
51
|
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
|
52
52
|
<Defs>
|
|
53
53
|
<LinearGradient id={`${gradientId}-top`} x1="0" y1="0" x2="0" y2="1">
|
|
@@ -59,8 +59,12 @@ const TimePickerWheel = ({ value, setValue = () => {}, items }: TimePickerWheelP
|
|
|
59
59
|
</Svg>
|
|
60
60
|
</View>
|
|
61
61
|
<View
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
style={[
|
|
63
|
+
styles.fadeOverlay,
|
|
64
|
+
styles.fadeOverlayBottom,
|
|
65
|
+
styles.pointerEventsNone,
|
|
66
|
+
{ height: fadeHeight },
|
|
67
|
+
]}
|
|
64
68
|
>
|
|
65
69
|
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
|
66
70
|
<Defs>
|
|
@@ -149,6 +153,9 @@ const styles = StyleSheet.create(theme => ({
|
|
|
149
153
|
top: undefined,
|
|
150
154
|
bottom: 0,
|
|
151
155
|
},
|
|
156
|
+
pointerEventsNone: {
|
|
157
|
+
pointerEvents: 'none',
|
|
158
|
+
},
|
|
152
159
|
}));
|
|
153
160
|
|
|
154
161
|
export default TimePickerWheel;
|
|
@@ -85,7 +85,7 @@ export const ToastProvider: React.FC<ToastProviderProps> = ({
|
|
|
85
85
|
return (
|
|
86
86
|
<ToastContext.Provider value={{ addToast, removeToast }}>
|
|
87
87
|
{children}
|
|
88
|
-
<View
|
|
88
|
+
<View style={styles.container as any}>
|
|
89
89
|
<View style={styles.stack as any}>
|
|
90
90
|
{toasts.map(t => (
|
|
91
91
|
<ToastItem
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
import { InfoMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
3
|
import { useRef, useState } from 'react';
|
|
4
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
4
5
|
import { VerificationInput, type VerificationInputHandle } from '.';
|
|
5
6
|
import { VariantTitle } from '../../../docs/components';
|
|
6
7
|
import { BodyText } from '../BodyText';
|
|
@@ -214,3 +215,35 @@ export const RefMethods: Story = {
|
|
|
214
215
|
);
|
|
215
216
|
},
|
|
216
217
|
};
|
|
218
|
+
|
|
219
|
+
export const FocusProgressionAfterEmptySlotSelection: Story = {
|
|
220
|
+
parameters: {
|
|
221
|
+
controls: { include: [] },
|
|
222
|
+
},
|
|
223
|
+
render: () => {
|
|
224
|
+
const [value, setValue] = useState('12');
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<Flex direction="column" spacing="sm" style={{ width: 400 }}>
|
|
228
|
+
<VerificationInput label="Verification Code" value={value} onChangeText={setValue} />
|
|
229
|
+
</Flex>
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
play: async ({ canvasElement }) => {
|
|
233
|
+
const canvas = within(canvasElement);
|
|
234
|
+
const input = canvas.getByLabelText('Verification Code') as HTMLInputElement;
|
|
235
|
+
|
|
236
|
+
input.focus();
|
|
237
|
+
|
|
238
|
+
input.setSelectionRange(4, 4);
|
|
239
|
+
input.dispatchEvent(new Event('select', { bubbles: true }));
|
|
240
|
+
|
|
241
|
+
await userEvent.keyboard('3');
|
|
242
|
+
|
|
243
|
+
await waitFor(() => {
|
|
244
|
+
expect(input.value).toBe('123');
|
|
245
|
+
expect(input.selectionStart).toBe(3);
|
|
246
|
+
expect(input.selectionEnd).toBe(3);
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
+
import { TextInput, View } from 'react-native';
|
|
3
3
|
import { StyleSheet } from 'react-native-unistyles';
|
|
4
4
|
import { FormField } from '../FormField';
|
|
5
5
|
import type { VerificationInputHandle, VerificationInputProps } from './VerificationInput.props';
|
|
6
|
+
import { getNextIndexFromValueChange } from './VerificationInput.utils';
|
|
6
7
|
import { VerificationInputSlot } from './VerificationInputSlot';
|
|
7
8
|
|
|
8
9
|
const VerificationInput = forwardRef<VerificationInputHandle, VerificationInputProps>(
|
|
@@ -22,6 +23,7 @@ const VerificationInput = forwardRef<VerificationInputHandle, VerificationInputP
|
|
|
22
23
|
secureTextEntry = false,
|
|
23
24
|
autoFocus = false,
|
|
24
25
|
style,
|
|
26
|
+
testID,
|
|
25
27
|
...props
|
|
26
28
|
},
|
|
27
29
|
ref
|
|
@@ -49,12 +51,15 @@ const VerificationInput = forwardRef<VerificationInputHandle, VerificationInputP
|
|
|
49
51
|
}
|
|
50
52
|
}, [length, value]);
|
|
51
53
|
|
|
52
|
-
const updateValue = (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
const updateValue = useCallback(
|
|
55
|
+
(nextValue: string) => {
|
|
56
|
+
const trimmedValue = nextValue.slice(0, length);
|
|
57
|
+
latestValueRef.current = trimmedValue;
|
|
58
|
+
setDisplayValue(trimmedValue);
|
|
59
|
+
onChangeText?.(trimmedValue);
|
|
60
|
+
},
|
|
61
|
+
[length, onChangeText]
|
|
62
|
+
);
|
|
58
63
|
|
|
59
64
|
const setSelectionIndex = (index: number) => {
|
|
60
65
|
const clampedIndex = Math.max(0, Math.min(index, length));
|
|
@@ -76,16 +81,6 @@ const VerificationInput = forwardRef<VerificationInputHandle, VerificationInputP
|
|
|
76
81
|
setFocusedIndex(Math.min(clampedIndex, length - 1));
|
|
77
82
|
};
|
|
78
83
|
|
|
79
|
-
const findDiffIndex = (prevValue: string, nextValue: string) => {
|
|
80
|
-
const minLength = Math.min(prevValue.length, nextValue.length);
|
|
81
|
-
for (let i = 0; i < minLength; i += 1) {
|
|
82
|
-
if (prevValue[i] !== nextValue[i]) {
|
|
83
|
-
return i;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return minLength;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
84
|
const handleChangeText = (text: string) => {
|
|
90
85
|
const prevValue = latestValueRef.current;
|
|
91
86
|
const nextValue = text.slice(0, length);
|
|
@@ -94,14 +89,7 @@ const VerificationInput = forwardRef<VerificationInputHandle, VerificationInputP
|
|
|
94
89
|
const diff = nextLength - prevLength;
|
|
95
90
|
const isBulkInsert = text.length > 1 && diff > 1;
|
|
96
91
|
const shouldBlur = nextLength >= length;
|
|
97
|
-
|
|
98
|
-
0,
|
|
99
|
-
Math.min(latestSelectionRef.current.start + (diff >= 0 ? 1 : diff), length)
|
|
100
|
-
);
|
|
101
|
-
if (Platform.OS === 'android') {
|
|
102
|
-
const editedIndex = findDiffIndex(prevValue, nextValue);
|
|
103
|
-
nextIndex = diff >= 0 ? Math.min(editedIndex + 1, length) : Math.max(editedIndex, 0);
|
|
104
|
-
}
|
|
92
|
+
const nextIndex = getNextIndexFromValueChange({ prevValue, nextValue, length });
|
|
105
93
|
updateValue(nextValue);
|
|
106
94
|
if (isBulkInsert) {
|
|
107
95
|
setCaretIndex(Math.min(nextLength, length));
|
|
@@ -171,7 +159,7 @@ const VerificationInput = forwardRef<VerificationInputHandle, VerificationInputP
|
|
|
171
159
|
}
|
|
172
160
|
},
|
|
173
161
|
}),
|
|
174
|
-
[length,
|
|
162
|
+
[length, updateValue]
|
|
175
163
|
);
|
|
176
164
|
|
|
177
165
|
const slots = Array.from({ length }, (_, index) => index);
|
|
@@ -252,7 +240,7 @@ const VerificationInput = forwardRef<VerificationInputHandle, VerificationInputP
|
|
|
252
240
|
maxLength={length}
|
|
253
241
|
caretHidden
|
|
254
242
|
style={styles.hiddenInput}
|
|
255
|
-
|
|
243
|
+
testID={testID}
|
|
256
244
|
/>
|
|
257
245
|
{slots.map(index => {
|
|
258
246
|
const char = displayValue[index] || '';
|
|
@@ -299,11 +287,12 @@ const styles = StyleSheet.create(theme => ({
|
|
|
299
287
|
position: 'absolute',
|
|
300
288
|
width: '100%',
|
|
301
289
|
height: '100%',
|
|
290
|
+
pointerEvents: 'none',
|
|
302
291
|
left: 0,
|
|
303
292
|
top: 0,
|
|
304
293
|
color: 'transparent',
|
|
305
294
|
fontSize: 1,
|
|
306
|
-
opacity: 0.
|
|
295
|
+
opacity: 0.01,
|
|
307
296
|
},
|
|
308
297
|
}));
|
|
309
298
|
|