@ecohouse/ui 0.1.32 → 0.1.34
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/dist/index.cjs +30063 -607
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +173 -10
- package/dist/index.d.ts +173 -10
- package/dist/index.js +29989 -547
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/AccordionItem/AccordionItem.stories.tsx +54 -0
- package/src/components/AccordionItem/AccordionItem.tsx +220 -0
- package/src/components/AccordionItem/index.ts +2 -0
- package/src/components/AccountHeader/AccountHeader.tsx +34 -29
- package/src/components/Button/Button.stories.tsx +10 -1
- package/src/components/Button/Button.tsx +32 -3
- package/src/components/ContactInfoCard/ContactInfoCard.stories.tsx +30 -0
- package/src/components/ContactInfoCard/ContactInfoCard.tsx +141 -0
- package/src/components/ContactInfoCard/index.ts +2 -0
- package/src/components/DatePicker/DatePicker.tsx +119 -46
- package/src/components/Modal/Modal.stories.tsx +100 -0
- package/src/components/Modal/Modal.tsx +165 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Select/Select.tsx +5 -2
- package/src/components/Sidebar/Sidebar.tsx +42 -12
- package/src/components/StepPager/StepPager.tsx +150 -0
- package/src/components/StepPager/index.ts +2 -0
- package/src/components/ToggleRow/ToggleRow.stories.tsx +46 -0
- package/src/components/ToggleRow/ToggleRow.tsx +121 -0
- package/src/components/ToggleRow/index.ts +2 -0
- package/src/components/VerificationCodeInput/VerificationCodeInput.tsx +80 -60
- package/src/components/index.ts +15 -0
- package/src/icons/Ban/BanIcon.tsx +15 -0
- package/src/icons/Ban/banPath.ts +2 -0
- package/src/icons/Ban/index.ts +1 -0
- package/src/icons/CameraFilled/CameraFilledIcon.tsx +15 -0
- package/src/icons/CameraFilled/CameraFilledIcon.web.tsx +21 -0
- package/src/icons/CameraFilled/cameraFilledPath.ts +2 -0
- package/src/icons/CameraFilled/index.ts +1 -0
- package/src/icons/Fullscreen/FullscreenIcon.tsx +21 -0
- package/src/icons/Fullscreen/fullscreenPath.ts +2 -0
- package/src/icons/Fullscreen/index.ts +1 -0
- package/src/icons/Headset/HeadsetIcon.tsx +26 -0
- package/src/icons/Headset/HeadsetIcon.web.tsx +25 -0
- package/src/icons/Headset/headsetPath.ts +6 -0
- package/src/icons/Headset/index.ts +1 -0
- package/src/icons/Hide/HideIcon.tsx +21 -0
- package/src/icons/Hide/HideIcon.web.tsx +27 -0
- package/src/icons/Hide/hidePath.ts +3 -0
- package/src/icons/Hide/index.ts +1 -0
- package/src/icons/Home/homePath.ts +1 -1
- package/src/icons/MenuLines/MenuLinesIcon.tsx +20 -0
- package/src/icons/MenuLines/MenuLinesIcon.web.tsx +26 -0
- package/src/icons/MenuLines/index.ts +1 -0
- package/src/icons/MenuLines/menuLinesPath.ts +3 -0
- package/src/icons/Sort/SortIcon.tsx +3 -3
- package/src/icons/Sort/SortIcon.web.tsx +2 -2
- package/src/icons/Sort/sortPath.ts +3 -2
- package/src/icons/VideoOff/VideoOffIcon.tsx +21 -0
- package/src/icons/VideoOff/VideoOffIcon.web.tsx +27 -0
- package/src/icons/VideoOff/index.ts +1 -0
- package/src/icons/VideoOff/videoOffPath.ts +2 -0
- package/src/icons/ZoomOut/ZoomOutIcon.tsx +21 -0
- package/src/icons/ZoomOut/index.ts +1 -0
- package/src/icons/ZoomOut/zoomOutPath.ts +2 -0
- package/src/icons/index.ts +8 -0
- package/src/icons/registry.ts +23 -0
- package/src/index.ts +20 -0
- package/src/theme/colors.test.ts +1 -1
- package/src/theme/typography.ts +4 -4
- package/src/utils/popoverAlign.ts +85 -0
|
@@ -61,6 +61,8 @@ type SelectBaseProps = Omit<ViewProps, "style" | "children"> & {
|
|
|
61
61
|
loading?: boolean;
|
|
62
62
|
/** Text shown when no options match. */
|
|
63
63
|
emptyText?: string;
|
|
64
|
+
/** Override color for the selected value text in the trigger. */
|
|
65
|
+
valueColor?: string;
|
|
64
66
|
style?: StyleProp<ViewStyle>;
|
|
65
67
|
className?: string;
|
|
66
68
|
};
|
|
@@ -264,6 +266,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
|
|
|
264
266
|
onSearchChange,
|
|
265
267
|
loading = false,
|
|
266
268
|
emptyText = "No results",
|
|
269
|
+
valueColor,
|
|
267
270
|
style,
|
|
268
271
|
className,
|
|
269
272
|
accessibilityLabel,
|
|
@@ -508,7 +511,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
|
|
|
508
511
|
|
|
509
512
|
const leftIconNode = renderStatefulIcon(leftIcon, UserIcon, {
|
|
510
513
|
size: ICON_SIZE,
|
|
511
|
-
color: error ? colors.red :
|
|
514
|
+
color: error ? colors.red : colors.primary,
|
|
512
515
|
});
|
|
513
516
|
const hasLeftIcon = showLeftIcon || leftIcon != null;
|
|
514
517
|
|
|
@@ -520,7 +523,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
|
|
|
520
523
|
const triggerTextColor = hasValue
|
|
521
524
|
? error
|
|
522
525
|
? colors.red
|
|
523
|
-
: colors.white
|
|
526
|
+
: (valueColor ?? colors.white)
|
|
524
527
|
: error
|
|
525
528
|
? colors.red
|
|
526
529
|
: colors.grey100;
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type ViewStyle,
|
|
19
19
|
} from "react-native";
|
|
20
20
|
import type { IconComponent } from "../../icons";
|
|
21
|
-
import { SidebarIcon } from "../../icons";
|
|
21
|
+
import { CloseIcon, SidebarIcon } from "../../icons";
|
|
22
22
|
import { BrandLogo, MenuItem } from "../../primitives";
|
|
23
23
|
import { colors, fonts } from "../../theme";
|
|
24
24
|
import { mergeRefs } from "../../utils/mergeRefs";
|
|
@@ -59,6 +59,12 @@ export interface SidebarProps extends Omit<ViewProps, "style" | "children"> {
|
|
|
59
59
|
collapsed?: boolean;
|
|
60
60
|
defaultCollapsed?: boolean;
|
|
61
61
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
62
|
+
/** When false, hides the expand/collapse control. Defaults to `true`. */
|
|
63
|
+
showCollapseToggle?: boolean;
|
|
64
|
+
/** When set, shows a close control in the header (e.g. mobile drawer). */
|
|
65
|
+
onClose?: () => void;
|
|
66
|
+
/** Accessible label for the close control. Defaults to `"Close"`. */
|
|
67
|
+
closeAccessibilityLabel?: string;
|
|
62
68
|
style?: StyleProp<ViewStyle>;
|
|
63
69
|
className?: string;
|
|
64
70
|
}
|
|
@@ -86,6 +92,9 @@ export const Sidebar = forwardRef<ComponentRef<typeof View>, SidebarProps>(funct
|
|
|
86
92
|
collapsed: collapsedProp,
|
|
87
93
|
defaultCollapsed = false,
|
|
88
94
|
onCollapsedChange,
|
|
95
|
+
showCollapseToggle = true,
|
|
96
|
+
onClose,
|
|
97
|
+
closeAccessibilityLabel = "Close",
|
|
89
98
|
style,
|
|
90
99
|
className,
|
|
91
100
|
...rest
|
|
@@ -136,7 +145,13 @@ export const Sidebar = forwardRef<ComponentRef<typeof View>, SidebarProps>(funct
|
|
|
136
145
|
style={[styles.root, { width: widthAnim }, style]}
|
|
137
146
|
accessibilityRole="menu"
|
|
138
147
|
>
|
|
139
|
-
<View
|
|
148
|
+
<View
|
|
149
|
+
style={[
|
|
150
|
+
styles.header,
|
|
151
|
+
collapsed ? styles.headerCollapsed : styles.headerExpanded,
|
|
152
|
+
!showCollapseToggle && !onClose ? styles.headerNoToggle : null,
|
|
153
|
+
]}
|
|
154
|
+
>
|
|
140
155
|
<Animated.View
|
|
141
156
|
pointerEvents={collapsed ? "none" : "auto"}
|
|
142
157
|
style={[
|
|
@@ -157,16 +172,28 @@ export const Sidebar = forwardRef<ComponentRef<typeof View>, SidebarProps>(funct
|
|
|
157
172
|
/>
|
|
158
173
|
</Animated.View>
|
|
159
174
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
{onClose ? (
|
|
176
|
+
<Pressable
|
|
177
|
+
accessibilityRole="button"
|
|
178
|
+
accessibilityLabel={closeAccessibilityLabel}
|
|
179
|
+
hitSlop={8}
|
|
180
|
+
onPress={onClose}
|
|
181
|
+
style={styles.toggleButton}
|
|
182
|
+
>
|
|
183
|
+
<CloseIcon color={colors.grey100} size={TOGGLE_SIZE} strokeWidth={2} />
|
|
184
|
+
</Pressable>
|
|
185
|
+
) : showCollapseToggle ? (
|
|
186
|
+
<Pressable
|
|
187
|
+
accessibilityRole="button"
|
|
188
|
+
accessibilityLabel={collapsed ? expandAccessibilityLabel : collapseAccessibilityLabel}
|
|
189
|
+
accessibilityState={{ expanded: !collapsed }}
|
|
190
|
+
hitSlop={8}
|
|
191
|
+
onPress={() => setCollapsed(!collapsed)}
|
|
192
|
+
style={styles.toggleButton}
|
|
193
|
+
>
|
|
194
|
+
<SidebarIcon color={colors.grey100} size={TOGGLE_SIZE} strokeWidth={2} />
|
|
195
|
+
</Pressable>
|
|
196
|
+
) : null}
|
|
170
197
|
</View>
|
|
171
198
|
|
|
172
199
|
<View
|
|
@@ -242,6 +269,9 @@ const styles = StyleSheet.create({
|
|
|
242
269
|
justifyContent: "space-between",
|
|
243
270
|
paddingHorizontal: 24,
|
|
244
271
|
},
|
|
272
|
+
headerNoToggle: {
|
|
273
|
+
justifyContent: "flex-start",
|
|
274
|
+
},
|
|
245
275
|
headerCollapsed: {
|
|
246
276
|
justifyContent: "center",
|
|
247
277
|
paddingHorizontal: 0,
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { forwardRef, useMemo, useRef, type ComponentRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewProps,
|
|
10
|
+
type ViewStyle,
|
|
11
|
+
} from "react-native";
|
|
12
|
+
import { ChevronLeftIcon } from "../../icons";
|
|
13
|
+
import { colors, fonts } from "../../theme";
|
|
14
|
+
import { mergeRefs } from "../../utils/mergeRefs";
|
|
15
|
+
import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
16
|
+
|
|
17
|
+
export interface StepPagerProps extends Omit<ViewProps, "style" | "children"> {
|
|
18
|
+
/** Zero-based active step index. */
|
|
19
|
+
index: number;
|
|
20
|
+
/** Total number of steps. */
|
|
21
|
+
count: number;
|
|
22
|
+
onPrevious?: () => void;
|
|
23
|
+
onNext?: () => void;
|
|
24
|
+
previousAccessibilityLabel?: string;
|
|
25
|
+
nextAccessibilityLabel?: string;
|
|
26
|
+
style?: StyleProp<ViewStyle>;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const WIDTH = 121;
|
|
31
|
+
const HEIGHT = 48;
|
|
32
|
+
const ICON_SIZE = 24;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compact prev / current-of-total / next control (e.g. camera fullscreen pager).
|
|
36
|
+
* Fixed 121×48 pill, 12 gap/padding, 77 radius.
|
|
37
|
+
*/
|
|
38
|
+
export const StepPager = forwardRef<ComponentRef<typeof View>, StepPagerProps>(function StepPager(
|
|
39
|
+
{
|
|
40
|
+
index,
|
|
41
|
+
count,
|
|
42
|
+
onPrevious,
|
|
43
|
+
onNext,
|
|
44
|
+
previousAccessibilityLabel = "Previous",
|
|
45
|
+
nextAccessibilityLabel = "Next",
|
|
46
|
+
style,
|
|
47
|
+
className,
|
|
48
|
+
accessibilityLabel,
|
|
49
|
+
...rest
|
|
50
|
+
},
|
|
51
|
+
forwardedRef,
|
|
52
|
+
) {
|
|
53
|
+
const containerRef = useRef<ComponentRef<typeof View>>(null);
|
|
54
|
+
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
55
|
+
const safeCount = Math.max(0, count);
|
|
56
|
+
const safeIndex = safeCount === 0 ? 0 : Math.min(Math.max(index, 0), safeCount - 1);
|
|
57
|
+
const label = safeCount === 0 ? "0/0" : `${safeIndex + 1}/${safeCount}`;
|
|
58
|
+
const atStart = safeIndex <= 0;
|
|
59
|
+
const atEnd = safeIndex >= safeCount - 1;
|
|
60
|
+
|
|
61
|
+
useApplyWebClassName(containerRef, className);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<View
|
|
65
|
+
{...rest}
|
|
66
|
+
ref={setContainerRef}
|
|
67
|
+
accessibilityRole="adjustable"
|
|
68
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
69
|
+
accessibilityValue={{
|
|
70
|
+
min: safeCount === 0 ? 0 : 1,
|
|
71
|
+
max: safeCount,
|
|
72
|
+
now: safeCount === 0 ? 0 : safeIndex + 1,
|
|
73
|
+
text: label,
|
|
74
|
+
}}
|
|
75
|
+
style={[styles.root, webBlurStyle, style]}
|
|
76
|
+
>
|
|
77
|
+
<Pressable
|
|
78
|
+
onPress={onPrevious}
|
|
79
|
+
disabled={atStart || !onPrevious}
|
|
80
|
+
hitSlop={4}
|
|
81
|
+
accessibilityRole="button"
|
|
82
|
+
accessibilityLabel={previousAccessibilityLabel}
|
|
83
|
+
accessibilityState={{ disabled: atStart || !onPrevious }}
|
|
84
|
+
style={[styles.navButton, (atStart || !onPrevious) && styles.navDisabled]}
|
|
85
|
+
>
|
|
86
|
+
<ChevronLeftIcon size={ICON_SIZE} color={colors.white} strokeWidth={2} />
|
|
87
|
+
</Pressable>
|
|
88
|
+
|
|
89
|
+
<Text style={styles.label}>{label}</Text>
|
|
90
|
+
|
|
91
|
+
<Pressable
|
|
92
|
+
onPress={onNext}
|
|
93
|
+
disabled={atEnd || !onNext}
|
|
94
|
+
hitSlop={4}
|
|
95
|
+
accessibilityRole="button"
|
|
96
|
+
accessibilityLabel={nextAccessibilityLabel}
|
|
97
|
+
accessibilityState={{ disabled: atEnd || !onNext }}
|
|
98
|
+
style={[styles.navButton, (atEnd || !onNext) && styles.navDisabled]}
|
|
99
|
+
>
|
|
100
|
+
<View style={styles.chevronRight}>
|
|
101
|
+
<ChevronLeftIcon size={ICON_SIZE} color={colors.white} strokeWidth={2} />
|
|
102
|
+
</View>
|
|
103
|
+
</Pressable>
|
|
104
|
+
</View>
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const webBlurStyle: ViewStyle =
|
|
109
|
+
Platform.OS === "web"
|
|
110
|
+
? ({
|
|
111
|
+
backdropFilter: "blur(50px)",
|
|
112
|
+
WebkitBackdropFilter: "blur(50px)",
|
|
113
|
+
} as ViewStyle)
|
|
114
|
+
: {};
|
|
115
|
+
|
|
116
|
+
const styles = StyleSheet.create({
|
|
117
|
+
root: {
|
|
118
|
+
width: WIDTH,
|
|
119
|
+
height: HEIGHT,
|
|
120
|
+
flexDirection: "row",
|
|
121
|
+
alignItems: "center",
|
|
122
|
+
justifyContent: "center",
|
|
123
|
+
gap: 12,
|
|
124
|
+
padding: 12,
|
|
125
|
+
borderRadius: 77,
|
|
126
|
+
backgroundColor: colors.whiteAlpha6,
|
|
127
|
+
overflow: "hidden",
|
|
128
|
+
},
|
|
129
|
+
navButton: {
|
|
130
|
+
width: ICON_SIZE,
|
|
131
|
+
height: ICON_SIZE,
|
|
132
|
+
alignItems: "center",
|
|
133
|
+
justifyContent: "center",
|
|
134
|
+
},
|
|
135
|
+
navDisabled: {
|
|
136
|
+
opacity: 0.35,
|
|
137
|
+
},
|
|
138
|
+
chevronRight: {
|
|
139
|
+
transform: [{ scaleX: -1 }],
|
|
140
|
+
},
|
|
141
|
+
label: {
|
|
142
|
+
fontFamily: fonts.sans,
|
|
143
|
+
fontSize: 16,
|
|
144
|
+
fontWeight: "700",
|
|
145
|
+
lineHeight: 16,
|
|
146
|
+
letterSpacing: 0,
|
|
147
|
+
color: colors.white,
|
|
148
|
+
textAlign: "center",
|
|
149
|
+
},
|
|
150
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { fn } from "storybook/test";
|
|
3
|
+
import { View, StyleSheet } from "react-native";
|
|
4
|
+
import { ToggleRow } from "./ToggleRow";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Components/ToggleRow",
|
|
8
|
+
component: ToggleRow,
|
|
9
|
+
args: {
|
|
10
|
+
label: "Bookings and digital key",
|
|
11
|
+
defaultValue: false,
|
|
12
|
+
onValueChange: fn(),
|
|
13
|
+
},
|
|
14
|
+
parameters: {
|
|
15
|
+
backgrounds: { default: "black" },
|
|
16
|
+
},
|
|
17
|
+
} satisfies Meta<typeof ToggleRow>;
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof meta>;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {};
|
|
23
|
+
|
|
24
|
+
export const Active: Story = {
|
|
25
|
+
args: { defaultValue: true },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Stack: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<View style={styles.stack}>
|
|
31
|
+
<ToggleRow label="Bookings and digital key" defaultValue={false} onValueChange={fn()} />
|
|
32
|
+
<ToggleRow
|
|
33
|
+
label="Email notifications"
|
|
34
|
+
defaultValue={false}
|
|
35
|
+
onValueChange={fn()}
|
|
36
|
+
showBorder={false}
|
|
37
|
+
/>
|
|
38
|
+
</View>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const styles = StyleSheet.create({
|
|
43
|
+
stack: {
|
|
44
|
+
width: 462,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { forwardRef, useMemo, useRef, type ComponentRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Text,
|
|
5
|
+
useWindowDimensions,
|
|
6
|
+
View,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type ViewProps,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { colors, fonts } from "../../theme";
|
|
12
|
+
import { mergeRefs } from "../../utils/mergeRefs";
|
|
13
|
+
import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
14
|
+
import { Toggle } from "../Toggle";
|
|
15
|
+
|
|
16
|
+
export interface ToggleRowProps extends Omit<ViewProps, "style" | "children"> {
|
|
17
|
+
label: string;
|
|
18
|
+
value?: boolean;
|
|
19
|
+
defaultValue?: boolean;
|
|
20
|
+
onValueChange?: (value: boolean) => void;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** When false, hides the bottom border. Defaults to true. */
|
|
23
|
+
showBorder?: boolean;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DESKTOP_MIN_WIDTH = 1024;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Settings toggle line — label left, switch right.
|
|
32
|
+
* Mobile: height 57, padding 16/12, label 12px.
|
|
33
|
+
* Desktop: height 65, padding 20/12, label 14px.
|
|
34
|
+
*/
|
|
35
|
+
export const ToggleRow = forwardRef<ComponentRef<typeof View>, ToggleRowProps>(function ToggleRow(
|
|
36
|
+
{
|
|
37
|
+
label,
|
|
38
|
+
value,
|
|
39
|
+
defaultValue = false,
|
|
40
|
+
onValueChange,
|
|
41
|
+
disabled = false,
|
|
42
|
+
showBorder = true,
|
|
43
|
+
style,
|
|
44
|
+
className,
|
|
45
|
+
accessibilityLabel,
|
|
46
|
+
...rest
|
|
47
|
+
},
|
|
48
|
+
forwardedRef,
|
|
49
|
+
) {
|
|
50
|
+
const containerRef = useRef<ComponentRef<typeof View>>(null);
|
|
51
|
+
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
52
|
+
const { width: viewportWidth } = useWindowDimensions();
|
|
53
|
+
const isDesktop = viewportWidth >= DESKTOP_MIN_WIDTH;
|
|
54
|
+
const labelSize = isDesktop ? 14 : 12;
|
|
55
|
+
|
|
56
|
+
useApplyWebClassName(containerRef, className);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<View
|
|
60
|
+
{...rest}
|
|
61
|
+
ref={setContainerRef}
|
|
62
|
+
style={[
|
|
63
|
+
styles.root,
|
|
64
|
+
{
|
|
65
|
+
minHeight: isDesktop ? 65 : 57,
|
|
66
|
+
paddingTop: isDesktop ? 20 : 16,
|
|
67
|
+
paddingBottom: isDesktop ? 20 : 16,
|
|
68
|
+
},
|
|
69
|
+
!showBorder && styles.noBorder,
|
|
70
|
+
style,
|
|
71
|
+
]}
|
|
72
|
+
>
|
|
73
|
+
<Text
|
|
74
|
+
style={[
|
|
75
|
+
styles.label,
|
|
76
|
+
{
|
|
77
|
+
fontSize: labelSize,
|
|
78
|
+
lineHeight: isDesktop ? 20 : 18,
|
|
79
|
+
letterSpacing: labelSize * 0.03,
|
|
80
|
+
},
|
|
81
|
+
]}
|
|
82
|
+
numberOfLines={2}
|
|
83
|
+
>
|
|
84
|
+
{label}
|
|
85
|
+
</Text>
|
|
86
|
+
<Toggle
|
|
87
|
+
value={value}
|
|
88
|
+
defaultValue={defaultValue}
|
|
89
|
+
onValueChange={onValueChange}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
92
|
+
/>
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const styles = StyleSheet.create({
|
|
98
|
+
root: {
|
|
99
|
+
width: "100%",
|
|
100
|
+
flexDirection: "row",
|
|
101
|
+
alignItems: "center",
|
|
102
|
+
gap: 40,
|
|
103
|
+
paddingRight: 12,
|
|
104
|
+
paddingLeft: 12,
|
|
105
|
+
borderBottomWidth: 1,
|
|
106
|
+
borderBottomColor: colors.grey650,
|
|
107
|
+
opacity: 1,
|
|
108
|
+
},
|
|
109
|
+
noBorder: {
|
|
110
|
+
borderBottomWidth: 0,
|
|
111
|
+
},
|
|
112
|
+
label: {
|
|
113
|
+
flex: 1,
|
|
114
|
+
minWidth: 0,
|
|
115
|
+
fontFamily: fonts.sans,
|
|
116
|
+
fontWeight: "700",
|
|
117
|
+
color: colors.white,
|
|
118
|
+
// RN-web clips Armenian descenders when line boxes are tight.
|
|
119
|
+
overflow: "visible",
|
|
120
|
+
},
|
|
121
|
+
});
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
Platform,
|
|
11
11
|
StyleSheet,
|
|
12
|
+
Text,
|
|
12
13
|
TextInput,
|
|
13
14
|
useWindowDimensions,
|
|
14
15
|
View,
|
|
@@ -24,12 +25,13 @@ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
|
24
25
|
import { normalizeVerificationCode, updateVerificationCode } from "./VerificationCodeInput.utils";
|
|
25
26
|
|
|
26
27
|
const DEFAULT_LENGTH = 6;
|
|
27
|
-
const DESKTOP_CELL_WIDTH = 54;
|
|
28
|
-
const MOBILE_CELL_WIDTH =
|
|
29
|
-
const
|
|
28
|
+
const DESKTOP_CELL_WIDTH = 54.333335876464844;
|
|
29
|
+
const MOBILE_CELL_WIDTH = 45.16666793823242;
|
|
30
|
+
const DESKTOP_CELL_HEIGHT = 68;
|
|
31
|
+
const MOBILE_CELL_HEIGHT = 58;
|
|
30
32
|
const DESKTOP_GAP = 12;
|
|
31
|
-
const MOBILE_GAP =
|
|
32
|
-
const MOBILE_BREAKPOINT =
|
|
33
|
+
const MOBILE_GAP = 12;
|
|
34
|
+
const MOBILE_BREAKPOINT = 1024;
|
|
33
35
|
|
|
34
36
|
export interface VerificationCodeInputHandle {
|
|
35
37
|
clear: () => void;
|
|
@@ -47,6 +49,8 @@ export interface VerificationCodeInputProps extends Omit<ViewProps, "children" |
|
|
|
47
49
|
/** Called when Enter/submit is pressed from a cell. */
|
|
48
50
|
onSubmit?: (value: string) => void;
|
|
49
51
|
error?: boolean;
|
|
52
|
+
/** Shown under the cells when `error` is true. */
|
|
53
|
+
errorMessage?: string;
|
|
50
54
|
disabled?: boolean;
|
|
51
55
|
autoFocus?: boolean;
|
|
52
56
|
/** Localized accessible name for each zero-based cell index. */
|
|
@@ -68,6 +72,7 @@ export const VerificationCodeInput = forwardRef<
|
|
|
68
72
|
onComplete,
|
|
69
73
|
onSubmit,
|
|
70
74
|
error = false,
|
|
75
|
+
errorMessage,
|
|
71
76
|
disabled = false,
|
|
72
77
|
autoFocus = false,
|
|
73
78
|
getDigitAccessibilityLabel,
|
|
@@ -161,71 +166,77 @@ export const VerificationCodeInput = forwardRef<
|
|
|
161
166
|
|
|
162
167
|
const mobile = viewportWidth < MOBILE_BREAKPOINT;
|
|
163
168
|
const cellWidth = mobile ? MOBILE_CELL_WIDTH : DESKTOP_CELL_WIDTH;
|
|
169
|
+
const cellHeight = mobile ? MOBILE_CELL_HEIGHT : DESKTOP_CELL_HEIGHT;
|
|
164
170
|
const cellGap = mobile ? MOBILE_GAP : DESKTOP_GAP;
|
|
165
|
-
const
|
|
171
|
+
const rowWidth = cellWidth * safeLength + cellGap * Math.max(0, safeLength - 1);
|
|
172
|
+
const showErrorMessage = Boolean(error && errorMessage);
|
|
166
173
|
|
|
167
174
|
return (
|
|
168
|
-
<View
|
|
169
|
-
{
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
})}
|
|
175
|
+
<View {...viewProps} ref={rootRef} style={[styles.wrapper, disabled && styles.disabled, style]}>
|
|
176
|
+
<View style={[styles.row, { width: rowWidth, maxWidth: "100%", gap: cellGap }]}>
|
|
177
|
+
{Array.from({ length: safeLength }, (_, index) => {
|
|
178
|
+
const active = focusedIndex === index;
|
|
179
|
+
const borderColor = error ? colors.red : active ? colors.primary : colors.grey600;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<TextInput
|
|
183
|
+
ref={(element) => {
|
|
184
|
+
inputRefs.current[index] = element;
|
|
185
|
+
}}
|
|
186
|
+
accessibilityLabel={
|
|
187
|
+
getDigitAccessibilityLabel?.(index) ?? `Verification code digit ${index + 1}`
|
|
188
|
+
}
|
|
189
|
+
accessibilityState={{ disabled }}
|
|
190
|
+
autoComplete={index === 0 ? "one-time-code" : "off"}
|
|
191
|
+
autoFocus={autoFocus && index === 0}
|
|
192
|
+
caretHidden={Platform.OS !== "web"}
|
|
193
|
+
editable={!disabled}
|
|
194
|
+
inputMode="numeric"
|
|
195
|
+
key={index}
|
|
196
|
+
keyboardType="number-pad"
|
|
197
|
+
maxLength={safeLength}
|
|
198
|
+
onBlur={() => setFocusedIndex((current) => (current === index ? null : current))}
|
|
199
|
+
onChangeText={(text) => handleChange(index, text)}
|
|
200
|
+
onFocus={() => setFocusedIndex(index)}
|
|
201
|
+
onKeyPress={(event) => handleKeyPress(index, event)}
|
|
202
|
+
onSubmitEditing={() => onSubmit?.(currentCode)}
|
|
203
|
+
selectTextOnFocus
|
|
204
|
+
style={[
|
|
205
|
+
styles.cell,
|
|
206
|
+
{
|
|
207
|
+
width: cellWidth,
|
|
208
|
+
height: cellHeight,
|
|
209
|
+
borderColor,
|
|
210
|
+
color: error ? colors.red : colors.white,
|
|
211
|
+
},
|
|
212
|
+
Platform.OS === "web" ? styles.cellWeb : null,
|
|
213
|
+
Platform.OS === "android" ? styles.cellAndroid : null,
|
|
214
|
+
inputStyle,
|
|
215
|
+
]}
|
|
216
|
+
value={currentCode[index] ?? ""}
|
|
217
|
+
/>
|
|
218
|
+
);
|
|
219
|
+
})}
|
|
220
|
+
</View>
|
|
221
|
+
|
|
222
|
+
{showErrorMessage ? (
|
|
223
|
+
<Text accessibilityLiveRegion="assertive" style={styles.errorMessage}>
|
|
224
|
+
{errorMessage}
|
|
225
|
+
</Text>
|
|
226
|
+
) : null}
|
|
221
227
|
</View>
|
|
222
228
|
);
|
|
223
229
|
});
|
|
224
230
|
|
|
225
231
|
const styles = StyleSheet.create({
|
|
226
|
-
|
|
232
|
+
wrapper: {
|
|
227
233
|
minWidth: 0,
|
|
228
234
|
alignSelf: "center",
|
|
235
|
+
alignItems: "center",
|
|
236
|
+
gap: 12,
|
|
237
|
+
},
|
|
238
|
+
row: {
|
|
239
|
+
minWidth: 0,
|
|
229
240
|
flexDirection: "row",
|
|
230
241
|
justifyContent: "center",
|
|
231
242
|
},
|
|
@@ -252,4 +263,13 @@ const styles = StyleSheet.create({
|
|
|
252
263
|
cellAndroid: {
|
|
253
264
|
includeFontPadding: false,
|
|
254
265
|
},
|
|
266
|
+
errorMessage: {
|
|
267
|
+
fontFamily: fonts.sans,
|
|
268
|
+
fontSize: 12,
|
|
269
|
+
fontWeight: "400",
|
|
270
|
+
lineHeight: 12,
|
|
271
|
+
letterSpacing: 0,
|
|
272
|
+
textAlign: "center",
|
|
273
|
+
color: colors.red,
|
|
274
|
+
},
|
|
255
275
|
});
|
package/src/components/index.ts
CHANGED
|
@@ -16,9 +16,18 @@ export type { StatusBadgeProps, StatusBadgeTone } from "./StatusBadge";
|
|
|
16
16
|
export { PaginationDots } from "./PaginationDots";
|
|
17
17
|
export type { PaginationDotsProps } from "./PaginationDots";
|
|
18
18
|
|
|
19
|
+
export { StepPager } from "./StepPager";
|
|
20
|
+
export type { StepPagerProps } from "./StepPager";
|
|
21
|
+
|
|
19
22
|
export { InfoTile } from "./InfoTile";
|
|
20
23
|
export type { InfoTileProps } from "./InfoTile";
|
|
21
24
|
|
|
25
|
+
export { ContactInfoCard } from "./ContactInfoCard";
|
|
26
|
+
export type { ContactInfoCardProps } from "./ContactInfoCard";
|
|
27
|
+
|
|
28
|
+
export { AccordionItem } from "./AccordionItem";
|
|
29
|
+
export type { AccordionItemProps } from "./AccordionItem";
|
|
30
|
+
|
|
22
31
|
export { StepList } from "./StepList";
|
|
23
32
|
export type { StepListProps } from "./StepList";
|
|
24
33
|
|
|
@@ -55,6 +64,9 @@ export type {
|
|
|
55
64
|
export { FloatingActionButton } from "./FloatingActionButton";
|
|
56
65
|
export type { FloatingActionButtonProps } from "./FloatingActionButton";
|
|
57
66
|
|
|
67
|
+
export { Modal } from "./Modal";
|
|
68
|
+
export type { ModalProps } from "./Modal";
|
|
69
|
+
|
|
58
70
|
export { Input } from "./Input";
|
|
59
71
|
export type { InputIcon, InputProps, InputSize } from "./Input";
|
|
60
72
|
|
|
@@ -79,6 +91,9 @@ export type { TextareaProps } from "./Textarea";
|
|
|
79
91
|
export { Toggle } from "./Toggle";
|
|
80
92
|
export type { ToggleProps } from "./Toggle";
|
|
81
93
|
|
|
94
|
+
export { ToggleRow } from "./ToggleRow";
|
|
95
|
+
export type { ToggleRowProps } from "./ToggleRow";
|
|
96
|
+
|
|
82
97
|
export { RatingInput } from "./RatingInput";
|
|
83
98
|
export type { RatingInputProps } from "./RatingInput";
|
|
84
99
|
|