@scripso-homepad/ui 0.3.6 → 0.3.8

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.
Files changed (38) hide show
  1. package/README.md +238 -79
  2. package/dist/index.cjs +1225 -85
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +324 -18
  5. package/dist/index.d.ts +324 -18
  6. package/dist/index.js +1204 -89
  7. package/dist/index.js.map +1 -1
  8. package/package.json +18 -6
  9. package/src/components/Button.stories.tsx +77 -57
  10. package/src/components/Button.tsx +154 -123
  11. package/src/components/CountryCodeSelector.stories.tsx +61 -0
  12. package/src/components/CountryCodeSelector.tsx +273 -0
  13. package/src/components/Input.stories.tsx +126 -0
  14. package/src/components/Input.tsx +221 -0
  15. package/src/components/Label.tsx +56 -0
  16. package/src/components/PhoneInput.stories.tsx +85 -0
  17. package/src/components/PhoneInput.tsx +172 -0
  18. package/src/data/countries.ts +98 -0
  19. package/src/icons/ArrowUpRightIcon.tsx +29 -0
  20. package/src/icons/ArrowUpRightIcon.web.tsx +35 -0
  21. package/src/icons/ChevronDownIcon.tsx +29 -0
  22. package/src/icons/ChevronDownIcon.web.tsx +35 -0
  23. package/src/icons/EyeIcon.tsx +36 -0
  24. package/src/icons/EyeIcon.web.tsx +42 -0
  25. package/src/icons/EyeOffIcon.tsx +29 -0
  26. package/src/icons/EyeOffIcon.web.tsx +35 -0
  27. package/src/icons/KeyIcon.tsx +36 -0
  28. package/src/icons/KeyIcon.web.tsx +42 -0
  29. package/src/icons/arrowUpRightPath.ts +2 -0
  30. package/src/icons/chevronDownPath.ts +1 -0
  31. package/src/icons/eyeIconPaths.ts +8 -0
  32. package/src/icons/keyIconPaths.ts +5 -0
  33. package/src/index.ts +42 -0
  34. package/src/theme/input.ts +154 -0
  35. package/src/theme/tokens.ts +272 -0
  36. package/src/utils/countryDropdownScrollStyles.ts +52 -0
  37. package/src/utils/countryFlag.ts +9 -0
  38. package/src/utils/useApplyWebClassName.ts +3 -2
@@ -1,48 +1,112 @@
1
- import { useRef, type ComponentRef } from "react";
1
+ import { useRef, type ComponentRef, type ReactNode } from "react";
2
2
  import {
3
+ Platform,
3
4
  StyleSheet,
4
5
  Text,
5
6
  TouchableOpacity,
7
+ View,
6
8
  type GestureResponderEvent,
7
9
  type StyleProp,
8
10
  type TextStyle,
9
11
  type ViewStyle,
10
12
  } from "react-native";
13
+ import { ArrowUpRightIcon } from "../icons/ArrowUpRightIcon";
14
+ import { colors, buttonTypography } from "../theme/tokens";
11
15
  import { useApplyWebClassName } from "../utils/useApplyWebClassName";
12
16
 
13
- export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost";
14
- export type ButtonSize = "small" | "medium" | "large";
17
+ export type ButtonVariant = "white" | "primary" | "green" | "gray";
18
+ export type ButtonSize = "lg" | "sm";
15
19
 
16
20
  export interface ButtonProps {
17
- title: string;
21
+ title?: string;
22
+ children?: ReactNode;
18
23
  onPress: (event: GestureResponderEvent) => void;
19
24
  disabled?: boolean;
20
- /** Visual style preset. */
21
25
  variant?: ButtonVariant;
22
- /** Size preset. */
23
26
  size?: ButtonSize;
24
- /** Additional container styles (works on web and native). */
27
+ showIcon?: boolean;
28
+ /** Custom icon inside the badge. Defaults to `ArrowUpRightIcon` when `showIcon` is true. */
29
+ icon?: ReactNode;
25
30
  style?: StyleProp<ViewStyle>;
26
- /** Additional label styles (works on web and native). */
27
31
  textStyle?: StyleProp<TextStyle>;
28
- /**
29
- * CSS class names for the container (web: applied to the same DOM node as default styles).
30
- * On native: ignored unless using NativeWind with cssInterop.
31
- */
32
32
  className?: string;
33
- /**
34
- * CSS class names for the label (web).
35
- * On native: ignored unless using NativeWind with cssInterop.
36
- */
37
33
  textClassName?: string;
38
34
  }
39
35
 
36
+ type VariantStyleSet = {
37
+ backgroundColor: string;
38
+ textColor: string;
39
+ iconBadgeBackgroundColor: string;
40
+ iconColor: string;
41
+ };
42
+
43
+ const variantConfig: Record<ButtonVariant, VariantStyleSet> = {
44
+ white: {
45
+ backgroundColor: colors.white,
46
+ textColor: colors.slateBlue,
47
+ iconBadgeBackgroundColor: colors.stormGray150,
48
+ iconColor: colors.navy,
49
+ },
50
+ primary: {
51
+ backgroundColor: colors.navy,
52
+ textColor: colors.stormGray0,
53
+ iconBadgeBackgroundColor: colors.white,
54
+ iconColor: colors.navy,
55
+ },
56
+ green: {
57
+ backgroundColor: colors.green,
58
+ textColor: colors.stormGray0,
59
+ iconBadgeBackgroundColor: colors.white,
60
+ iconColor: colors.green,
61
+ },
62
+ gray: {
63
+ backgroundColor: colors.stormGray50,
64
+ textColor: colors.slateBlue,
65
+ iconBadgeBackgroundColor: colors.stormGray150,
66
+ iconColor: colors.navy,
67
+ },
68
+ };
69
+
70
+ const sizeConfig = {
71
+ lg: {
72
+ borderRadius: 16,
73
+ paddingTop: 8,
74
+ paddingRight: 8,
75
+ paddingBottom: 8,
76
+ paddingLeft: 24,
77
+ paddingHorizontalCentered: 24,
78
+ minHeight: 52,
79
+ text: buttonTypography.lg,
80
+ iconContainerSize: 36,
81
+ iconContainerRadius: 12,
82
+ iconContainerPadding: 8,
83
+ iconSize: 20,
84
+ },
85
+ sm: {
86
+ borderRadius: 12,
87
+ paddingTop: 8,
88
+ paddingRight: 8,
89
+ paddingBottom: 8,
90
+ paddingLeft: 16,
91
+ paddingHorizontalCentered: 16,
92
+ minHeight: 48,
93
+ text: buttonTypography.sm,
94
+ iconContainerSize: 32,
95
+ iconContainerRadius: 8,
96
+ iconContainerPadding: 8,
97
+ iconSize: 16,
98
+ },
99
+ } as const;
100
+
40
101
  export function Button({
41
102
  title,
103
+ children,
42
104
  onPress,
43
105
  disabled = false,
44
106
  variant = "primary",
45
- size = "medium",
107
+ size = "lg",
108
+ showIcon = false,
109
+ icon,
46
110
  style,
47
111
  textStyle,
48
112
  className,
@@ -50,23 +114,45 @@ export function Button({
50
114
  }: ButtonProps) {
51
115
  const containerRef = useRef<ComponentRef<typeof TouchableOpacity>>(null);
52
116
  const textRef = useRef<ComponentRef<typeof Text>>(null);
117
+ const preset = variantConfig[variant];
118
+ const metrics = sizeConfig[size];
53
119
 
54
120
  useApplyWebClassName(containerRef, className);
55
121
  useApplyWebClassName(textRef, textClassName);
56
122
 
123
+ const label = typeof children !== "undefined" ? children : title;
124
+ const hasCustomChildren = typeof children !== "undefined";
125
+ const customIcon =
126
+ icon != null && typeof icon !== "boolean" ? icon : undefined;
127
+ const hasIcon = showIcon || icon === true || customIcon != null;
128
+ const iconNode =
129
+ customIcon ?? (
130
+ <ArrowUpRightIcon size={metrics.iconSize} color={preset.iconColor} />
131
+ );
132
+
57
133
  const containerStyle = [
58
134
  styles.base,
59
- variantStyles[variant],
60
- sizeStyles[size],
61
- disabled && disabledVariantStyles[variant],
135
+ {
136
+ borderRadius: metrics.borderRadius,
137
+ backgroundColor: preset.backgroundColor,
138
+ minHeight: metrics.minHeight,
139
+ paddingTop: metrics.paddingTop,
140
+ paddingBottom: metrics.paddingBottom,
141
+ paddingLeft: hasIcon ? metrics.paddingLeft : metrics.paddingHorizontalCentered,
142
+ paddingRight: hasIcon ? metrics.paddingRight : metrics.paddingHorizontalCentered,
143
+ },
144
+ hasIcon ? styles.withIcon : styles.centered,
145
+ disabled && styles.disabled,
62
146
  style,
63
147
  ];
64
148
 
65
149
  const labelStyle = [
66
- textBaseStyles.base,
67
- textVariantStyles[variant],
68
- textSizeStyles[size],
69
- disabled && textDisabledStyles[variant],
150
+ styles.label,
151
+ metrics.text,
152
+ { color: preset.textColor },
153
+ Platform.OS === "android" ? styles.labelAndroid : null,
154
+ !hasIcon && styles.labelCentered,
155
+ hasIcon && styles.labelWithIcon,
70
156
  textStyle,
71
157
  ];
72
158
 
@@ -80,119 +166,64 @@ export function Button({
80
166
  accessibilityRole="button"
81
167
  accessibilityState={{ disabled }}
82
168
  >
83
- <Text ref={textRef} style={labelStyle}>
84
- {title}
85
- </Text>
169
+ {hasCustomChildren ? (
170
+ children
171
+ ) : (
172
+ <Text ref={textRef} style={labelStyle}>
173
+ {label}
174
+ </Text>
175
+ )}
176
+
177
+ {hasIcon ? (
178
+ <View
179
+ style={[
180
+ styles.iconContainer,
181
+ {
182
+ width: metrics.iconContainerSize,
183
+ height: metrics.iconContainerSize,
184
+ borderRadius: metrics.iconContainerRadius,
185
+ padding: metrics.iconContainerPadding,
186
+ backgroundColor: preset.iconBadgeBackgroundColor,
187
+ },
188
+ ]}
189
+ >
190
+ {iconNode}
191
+ </View>
192
+ ) : null}
86
193
  </TouchableOpacity>
87
194
  );
88
195
  }
89
196
 
90
197
  const styles = StyleSheet.create({
91
198
  base: {
199
+ flexDirection: "row",
92
200
  alignItems: "center",
93
- justifyContent: "center",
94
- borderRadius: 8,
95
201
  borderWidth: 0,
96
202
  },
97
- });
98
-
99
- const variantStyles = StyleSheet.create({
100
- primary: {
101
- backgroundColor: "#2563eb",
102
- },
103
- secondary: {
104
- backgroundColor: "#4b5563",
105
- },
106
- outline: {
107
- backgroundColor: "transparent",
108
- borderWidth: 1,
109
- borderColor: "#2563eb",
110
- },
111
- ghost: {
112
- backgroundColor: "transparent",
113
- },
114
- });
115
-
116
- const sizeStyles = StyleSheet.create({
117
- small: {
118
- paddingVertical: 8,
119
- paddingHorizontal: 16,
120
- minWidth: 96,
121
- },
122
- medium: {
123
- paddingVertical: 12,
124
- paddingHorizontal: 24,
125
- minWidth: 120,
126
- },
127
- large: {
128
- paddingVertical: 16,
129
- paddingHorizontal: 32,
130
- minWidth: 160,
131
- },
132
- });
133
-
134
- const disabledVariantStyles = StyleSheet.create({
135
- primary: {
136
- backgroundColor: "#93c5fd",
137
- opacity: 0.7,
138
- },
139
- secondary: {
140
- backgroundColor: "#9ca3af",
141
- opacity: 0.7,
142
- },
143
- outline: {
144
- borderColor: "#93c5fd",
145
- opacity: 0.7,
146
- },
147
- ghost: {
148
- opacity: 0.5,
149
- },
150
- });
151
-
152
- const textBaseStyles = StyleSheet.create({
153
- base: {
154
- fontWeight: "600",
155
- },
156
- });
157
-
158
- const textVariantStyles = StyleSheet.create({
159
- primary: {
160
- color: "#ffffff",
161
- },
162
- secondary: {
163
- color: "#ffffff",
203
+ centered: {
204
+ justifyContent: "center",
164
205
  },
165
- outline: {
166
- color: "#2563eb",
206
+ withIcon: {
207
+ justifyContent: "space-between",
167
208
  },
168
- ghost: {
169
- color: "#2563eb",
209
+ disabled: {
210
+ opacity: 0.6,
170
211
  },
171
- });
172
-
173
- const textSizeStyles = StyleSheet.create({
174
- small: {
175
- fontSize: 14,
212
+ label: {},
213
+ labelAndroid: {
214
+ includeFontPadding: false,
176
215
  },
177
- medium: {
178
- fontSize: 16,
216
+ labelCentered: {
217
+ textAlign: "center",
179
218
  },
180
- large: {
181
- fontSize: 18,
219
+ labelWithIcon: {
220
+ flex: 1,
221
+ flexShrink: 1,
222
+ marginRight: 8,
182
223
  },
183
- });
184
-
185
- const textDisabledStyles = StyleSheet.create({
186
- primary: {
187
- color: "#e5e7eb",
188
- },
189
- secondary: {
190
- color: "#f3f4f6",
191
- },
192
- outline: {
193
- color: "#93c5fd",
194
- },
195
- ghost: {
196
- color: "#93c5fd",
224
+ iconContainer: {
225
+ alignItems: "center",
226
+ justifyContent: "center",
227
+ flexShrink: 0,
197
228
  },
198
229
  });
@@ -0,0 +1,61 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { View, StyleSheet } from "react-native";
4
+ import { CountryCodeSelector } from "./CountryCodeSelector";
5
+
6
+ const meta = {
7
+ title: "Components/CountryCodeSelector",
8
+ component: CountryCodeSelector,
9
+ } satisfies Meta<typeof CountryCodeSelector>;
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof meta>;
13
+
14
+ export const Default: Story = {
15
+ render: () => (
16
+ <View style={styles.wrapper}>
17
+ <CountryCodeSelector />
18
+ </View>
19
+ ),
20
+ };
21
+
22
+ export const Controlled: Story = {
23
+ render: function ControlledCountryStory() {
24
+ const [code, setCode] = useState("AM");
25
+ return (
26
+ <View style={styles.wrapper}>
27
+ <CountryCodeSelector value={code} onValueChange={setCode} />
28
+ </View>
29
+ );
30
+ },
31
+ };
32
+
33
+ export const Disabled: Story = {
34
+ render: () => (
35
+ <View style={styles.wrapper}>
36
+ <CountryCodeSelector value="AM" disabled />
37
+ </View>
38
+ ),
39
+ };
40
+
41
+ /** Opens upward when near the bottom of the viewport. */
42
+ export const OpenNearBottom: Story = {
43
+ render: () => (
44
+ <View style={styles.bottomWrapper}>
45
+ <CountryCodeSelector />
46
+ </View>
47
+ ),
48
+ };
49
+
50
+ const styles = StyleSheet.create({
51
+ wrapper: {
52
+ alignItems: "flex-start",
53
+ },
54
+ bottomWrapper: {
55
+ flex: 1,
56
+ minHeight: 500,
57
+ justifyContent: "flex-end",
58
+ alignItems: "flex-start",
59
+ paddingBottom: 16,
60
+ },
61
+ });
@@ -0,0 +1,273 @@
1
+ import { useRef, useState, useLayoutEffect, type ComponentRef } from "react";
2
+ import {
3
+ Dimensions,
4
+ Modal,
5
+ Platform,
6
+ Pressable,
7
+ ScrollView,
8
+ StyleSheet,
9
+ Text,
10
+ View,
11
+ type StyleProp,
12
+ type ViewStyle,
13
+ } from "react-native";
14
+ import {
15
+ countries,
16
+ defaultCountry,
17
+ findCountry,
18
+ type Country,
19
+ } from "../data/countries";
20
+ import { ChevronDownIcon } from "../icons/ChevronDownIcon";
21
+ import { colors, fontSize, fontWeight, radii, spacing, fonts } from "../theme/tokens";
22
+ import { countryCodeToFlagEmoji } from "../utils/countryFlag";
23
+ import {
24
+ COUNTRY_DROPDOWN_SCROLL_CLASS,
25
+ ensureCountryDropdownScrollStyles,
26
+ } from "../utils/countryDropdownScrollStyles";
27
+ import { useApplyWebClassName } from "../utils/useApplyWebClassName";
28
+
29
+ const DROPDOWN_GAP = 10;
30
+ const DROPDOWN_HEIGHT = 186;
31
+ const DROPDOWN_PADDING = 8;
32
+ const OPTION_GAP = 10;
33
+ const OPTION_HEIGHT = 35;
34
+ const TRIGGER_HEIGHT = 52;
35
+ const CHEVRON_SIZE = 16;
36
+
37
+ export interface CountryCodeSelectorProps {
38
+ value?: string;
39
+ onValueChange?: (countryCode: string) => void;
40
+ /** Country list (defaults to built-in list). */
41
+ options?: Country[];
42
+ disabled?: boolean;
43
+ style?: StyleProp<ViewStyle>;
44
+ className?: string;
45
+ }
46
+
47
+ type AnchorLayout = {
48
+ x: number;
49
+ y: number;
50
+ width: number;
51
+ height: number;
52
+ };
53
+
54
+ function resolveDropdownTop(anchor: AnchorLayout): number {
55
+ const windowHeight = Dimensions.get("window").height;
56
+ const topBelow = anchor.y + anchor.height + DROPDOWN_GAP;
57
+ const topAbove = anchor.y - DROPDOWN_GAP - DROPDOWN_HEIGHT;
58
+ const fitsBelow = topBelow + DROPDOWN_HEIGHT <= windowHeight;
59
+
60
+ if (fitsBelow) return topBelow;
61
+ if (topAbove >= 0) return topAbove;
62
+
63
+ return Math.max(0, Math.min(topBelow, windowHeight - DROPDOWN_HEIGHT));
64
+ }
65
+
66
+ export function CountryCodeSelector({
67
+ value = defaultCountry.code,
68
+ onValueChange,
69
+ options = countries,
70
+ disabled = false,
71
+ style,
72
+ className,
73
+ }: CountryCodeSelectorProps) {
74
+ const wrapperRef = useRef<ComponentRef<typeof View>>(null);
75
+ const triggerRef = useRef<ComponentRef<typeof Pressable>>(null);
76
+ const scrollRef = useRef<ComponentRef<typeof ScrollView>>(null);
77
+ const [open, setOpen] = useState(false);
78
+ const [anchor, setAnchor] = useState<AnchorLayout | null>(null);
79
+
80
+ useApplyWebClassName(wrapperRef, className);
81
+ useApplyWebClassName(
82
+ scrollRef,
83
+ COUNTRY_DROPDOWN_SCROLL_CLASS,
84
+ open && Boolean(anchor),
85
+ );
86
+
87
+ useLayoutEffect(() => {
88
+ ensureCountryDropdownScrollStyles();
89
+ }, []);
90
+
91
+ const selected = findCountry(value);
92
+ const textColor = disabled ? colors.stormGray200 : colors.slateBlue;
93
+
94
+ function closeDropdown() {
95
+ setOpen(false);
96
+ setAnchor(null);
97
+ }
98
+
99
+ function handleOpen() {
100
+ if (disabled) return;
101
+
102
+ triggerRef.current?.measureInWindow((x, y, width, height) => {
103
+ setAnchor({ x, y, width, height });
104
+ setOpen(true);
105
+ });
106
+ }
107
+
108
+ function handleSelect(code: string) {
109
+ onValueChange?.(code);
110
+ closeDropdown();
111
+ }
112
+
113
+ const dropdownTop = anchor ? resolveDropdownTop(anchor) : 0;
114
+
115
+ const optionList = options.map((item, index) => {
116
+ const isSelected = item.code === selected.code;
117
+ const isLast = index === options.length - 1;
118
+ return (
119
+ <Pressable
120
+ key={item.code}
121
+ accessibilityRole="button"
122
+ onPress={() => handleSelect(item.code)}
123
+ style={[
124
+ styles.option,
125
+ isSelected && styles.optionSelected,
126
+ !isLast && styles.optionSpacing,
127
+ ]}
128
+ >
129
+ <Text style={styles.flag}>{countryCodeToFlagEmoji(item.code)}</Text>
130
+ <Text style={styles.optionDialCode}>{item.dialCode}</Text>
131
+ </Pressable>
132
+ );
133
+ });
134
+
135
+ const dropdownList = (
136
+ <ScrollView
137
+ ref={scrollRef}
138
+ style={styles.dropdownScroll}
139
+ contentContainerStyle={styles.dropdownContent}
140
+ keyboardShouldPersistTaps="handled"
141
+ showsVerticalScrollIndicator
142
+ bounces={false}
143
+ nestedScrollEnabled
144
+ >
145
+ {optionList}
146
+ </ScrollView>
147
+ );
148
+
149
+ return (
150
+ <View ref={wrapperRef} style={[styles.root, style]}>
151
+ <Pressable
152
+ ref={triggerRef}
153
+ accessibilityRole="button"
154
+ disabled={disabled}
155
+ onPress={handleOpen}
156
+ style={[
157
+ styles.trigger,
158
+ disabled ? styles.triggerDisabled : styles.triggerEnabled,
159
+ Platform.OS === "web" ? styles.triggerWeb : null,
160
+ ]}
161
+ >
162
+ <Text style={styles.flag}>{countryCodeToFlagEmoji(selected.code)}</Text>
163
+ <Text style={[styles.dialCode, { color: textColor }]}>{selected.dialCode}</Text>
164
+ <ChevronDownIcon size={CHEVRON_SIZE} color={colors.stormGray100} />
165
+ </Pressable>
166
+
167
+ <Modal
168
+ visible={open}
169
+ transparent
170
+ animationType="fade"
171
+ onRequestClose={closeDropdown}
172
+ >
173
+ <Pressable style={styles.overlay} onPress={closeDropdown}>
174
+ {anchor ? (
175
+ <View
176
+ style={[
177
+ styles.dropdown,
178
+ {
179
+ top: dropdownTop,
180
+ left: anchor.x,
181
+ width: anchor.width,
182
+ },
183
+ ]}
184
+ >
185
+ {dropdownList}
186
+ </View>
187
+ ) : null}
188
+ </Pressable>
189
+ </Modal>
190
+ </View>
191
+ );
192
+ }
193
+
194
+ const styles = StyleSheet.create({
195
+ root: {
196
+ alignSelf: "flex-start",
197
+ flexShrink: 0,
198
+ },
199
+ trigger: {
200
+ flexDirection: "row",
201
+ alignItems: "center",
202
+ alignSelf: "flex-start",
203
+ flexGrow: 0,
204
+ flexShrink: 0,
205
+ height: TRIGGER_HEIGHT,
206
+ borderRadius: radii.lg,
207
+ borderWidth: 1,
208
+ padding: spacing.lg,
209
+ gap: spacing.sm,
210
+ },
211
+ triggerEnabled: {
212
+ borderColor: colors.stormGray50,
213
+ backgroundColor: colors.white,
214
+ },
215
+ triggerDisabled: {
216
+ borderColor: colors.stormGray50,
217
+ backgroundColor: colors.stormGray50,
218
+ },
219
+ triggerWeb: {
220
+ width: "max-content" as ViewStyle["width"],
221
+ },
222
+ flag: {
223
+ fontSize: 18,
224
+ lineHeight: 22,
225
+ },
226
+ dialCode: {
227
+ fontSize: fontSize.md,
228
+ fontWeight: fontWeight.medium,
229
+ },
230
+ overlay: {
231
+ flex: 1,
232
+ backgroundColor: "transparent",
233
+ },
234
+ dropdown: {
235
+ position: "absolute",
236
+ height: DROPDOWN_HEIGHT,
237
+ borderRadius: radii.lg,
238
+ borderWidth: 1,
239
+ borderColor: colors.stormGray50,
240
+ backgroundColor: colors.white,
241
+ padding: DROPDOWN_PADDING,
242
+ overflow: "hidden",
243
+ },
244
+ dropdownScroll: {
245
+ flex: 1,
246
+ },
247
+ dropdownContent: {
248
+ flexGrow: 1,
249
+ },
250
+ option: {
251
+ flexDirection: "row",
252
+ alignItems: "center",
253
+ height: OPTION_HEIGHT,
254
+ borderRadius: radii.sm,
255
+ padding: spacing.sm,
256
+ gap: spacing.sm,
257
+ },
258
+ optionSpacing: {
259
+ marginBottom: OPTION_GAP,
260
+ },
261
+ optionSelected: {
262
+ backgroundColor: colors.countrySelectorSelectedBg,
263
+ },
264
+ optionDialCode: {
265
+ fontFamily: fonts.sans,
266
+ fontSize: fontSize.md,
267
+ fontWeight: fontWeight.regular,
268
+ lineHeight: fontSize.md,
269
+ letterSpacing: 0,
270
+ textAlign: "center",
271
+ color: colors.slateBlue,
272
+ },
273
+ });