@ecohouse/ui 0.1.36 → 0.1.39

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 (43) hide show
  1. package/README.md +2 -0
  2. package/dist/index.cjs +1169 -428
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +101 -2
  5. package/dist/index.d.ts +101 -2
  6. package/dist/index.js +1092 -358
  7. package/dist/index.js.map +1 -1
  8. package/package.json +4 -3
  9. package/src/components/Accordion/Accordion.stories.tsx +47 -0
  10. package/src/components/Accordion/Accordion.tsx +165 -0
  11. package/src/components/Accordion/index.ts +2 -0
  12. package/src/components/AutocompleteInput/AutocompleteInput.stories.tsx +80 -0
  13. package/src/components/AutocompleteInput/AutocompleteInput.tsx +313 -0
  14. package/src/components/AutocompleteInput/index.ts +2 -0
  15. package/src/components/Counter/Counter.tsx +61 -26
  16. package/src/components/Drawer/Drawer.stories.tsx +79 -0
  17. package/src/components/Drawer/Drawer.tsx +239 -0
  18. package/src/components/Drawer/index.ts +2 -0
  19. package/src/components/QuantityInput/QuantityInput.stories.tsx +45 -0
  20. package/src/components/QuantityInput/QuantityInput.tsx +153 -0
  21. package/src/components/QuantityInput/index.ts +2 -0
  22. package/src/components/StatusBadge/StatusBadge.tsx +1 -1
  23. package/src/components/index.ts +12 -0
  24. package/src/icons/ArrowLeft/ArrowLeftIcon.test.ts +10 -0
  25. package/src/icons/ArrowLeft/ArrowLeftIcon.tsx +21 -0
  26. package/src/icons/ArrowLeft/ArrowLeftIcon.web.tsx +26 -0
  27. package/src/icons/ArrowLeft/arrowLeftPath.ts +3 -0
  28. package/src/icons/ArrowLeft/index.ts +1 -0
  29. package/src/icons/CalendarMinus/CalendarMinusIcon.tsx +25 -0
  30. package/src/icons/CalendarMinus/CalendarMinusIcon.web.tsx +31 -0
  31. package/src/icons/CalendarMinus/calendarMinusPath.ts +3 -0
  32. package/src/icons/CalendarMinus/index.ts +1 -0
  33. package/src/icons/CurrentLocation/CurrentLocationIcon.test.ts +10 -0
  34. package/src/icons/CurrentLocation/CurrentLocationIcon.tsx +25 -0
  35. package/src/icons/CurrentLocation/CurrentLocationIcon.web.tsx +30 -0
  36. package/src/icons/CurrentLocation/currentLocationPath.ts +3 -0
  37. package/src/icons/CurrentLocation/index.ts +1 -0
  38. package/src/icons/index.ts +3 -0
  39. package/src/icons/registry.ts +3 -0
  40. package/src/index.ts +12 -0
  41. package/src/theme/colors.test.ts +1 -0
  42. package/src/theme/colors.ts +2 -0
  43. package/src/web/styles/account-header.css +22 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecohouse/ui",
3
- "version": "0.1.36",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Cross-platform presentation-only UI components for EcoHouse (React Native + React Native Web)",
@@ -26,7 +26,8 @@
26
26
  "default": "./src/index.ts"
27
27
  },
28
28
  "./web/fonts.css": "./src/web/styles/fonts.css",
29
- "./web/typography.css": "./src/web/styles/typography.css"
29
+ "./web/typography.css": "./src/web/styles/typography.css",
30
+ "./web/account-header.css": "./src/web/styles/account-header.css"
30
31
  },
31
32
  "files": [
32
33
  "dist",
@@ -54,7 +55,7 @@
54
55
  "storybook": "storybook dev -p 6006",
55
56
  "storybook:clean": "rm -rf node_modules/.cache/storybook node_modules/.vite storybook-static && storybook dev -p 6006",
56
57
  "build-storybook": "storybook build",
57
- "prepare": "husky"
58
+ "prepare": "node -e \"if (process.env.CI !== 'true' && process.env.HUSKY !== '0') require('child_process').execSync('husky', { stdio: 'inherit' })\""
58
59
  },
59
60
  "lint-staged": {
60
61
  "*.{js,jsx,ts,tsx,mjs}": [
@@ -0,0 +1,47 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import { Text, TextInput, View } from "react-native";
4
+ import { colors } from "../../theme";
5
+ import { Accordion } from "./Accordion";
6
+
7
+ const meta = {
8
+ title: "Components/Accordion",
9
+ component: Accordion,
10
+ args: {
11
+ title: "Russian",
12
+ },
13
+ parameters: {
14
+ backgrounds: { default: "black" },
15
+ },
16
+ } satisfies Meta<typeof Accordion>;
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ export const Closed: Story = {
22
+ args: {
23
+ children: <Text style={{ color: colors.white }}>Translated content</Text>,
24
+ },
25
+ };
26
+
27
+ function InteractiveContent() {
28
+ const [name, setName] = useState("");
29
+ return (
30
+ <View style={{ gap: 16 }}>
31
+ <Text style={{ color: colors.white }}>Cottage name</Text>
32
+ <TextInput
33
+ onChangeText={setName}
34
+ placeholder="e.g., Alpine Eco Retreat"
35
+ style={{ backgroundColor: colors.grey700, color: colors.white, padding: 12 }}
36
+ value={name}
37
+ />
38
+ </View>
39
+ );
40
+ }
41
+
42
+ export const WithInteractiveContent: Story = {
43
+ args: {
44
+ children: <InteractiveContent />,
45
+ defaultOpen: true,
46
+ },
47
+ };
@@ -0,0 +1,165 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ type ComponentRef,
8
+ type ReactNode,
9
+ } from "react";
10
+ import {
11
+ Pressable,
12
+ StyleSheet,
13
+ Text,
14
+ useWindowDimensions,
15
+ View,
16
+ type StyleProp,
17
+ type ViewProps,
18
+ type ViewStyle,
19
+ } from "react-native";
20
+ import { ChevronDownIcon } from "../../icons";
21
+ import { colors, fonts } from "../../theme";
22
+ import { mergeRefs } from "../../utils/mergeRefs";
23
+ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
24
+
25
+ export interface AccordionProps extends Omit<ViewProps, "style" | "children"> {
26
+ title: string;
27
+ children: ReactNode;
28
+ open?: boolean;
29
+ defaultOpen?: boolean;
30
+ onOpenChange?: (open: boolean) => void;
31
+ disabled?: boolean;
32
+ error?: boolean;
33
+ style?: StyleProp<ViewStyle>;
34
+ contentStyle?: StyleProp<ViewStyle>;
35
+ className?: string;
36
+ }
37
+
38
+ const DESKTOP_MIN_WIDTH = 1024;
39
+ const CHEVRON_SIZE = 24;
40
+
41
+ /** Expandable surface for arbitrary interactive content such as form fields. */
42
+ export const Accordion = forwardRef<ComponentRef<typeof View>, AccordionProps>(function Accordion(
43
+ {
44
+ title,
45
+ children,
46
+ open: openProp,
47
+ defaultOpen = false,
48
+ onOpenChange,
49
+ disabled = false,
50
+ error = false,
51
+ style,
52
+ contentStyle,
53
+ className,
54
+ accessibilityLabel,
55
+ ...rest
56
+ },
57
+ forwardedRef,
58
+ ) {
59
+ const rootRef = useRef<ComponentRef<typeof View>>(null);
60
+ const setRootRef = useMemo(() => mergeRefs(rootRef, forwardedRef), [forwardedRef]);
61
+ const controlled = openProp !== undefined;
62
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
63
+ const open = controlled ? Boolean(openProp) : uncontrolledOpen;
64
+ const { width } = useWindowDimensions();
65
+ const desktop = width >= DESKTOP_MIN_WIDTH;
66
+ const horizontalPadding = desktop ? 24 : 16;
67
+
68
+ useApplyWebClassName(rootRef, className?.trim() || undefined);
69
+
70
+ const toggle = useCallback(() => {
71
+ if (disabled) return;
72
+ const next = !open;
73
+ if (!controlled) setUncontrolledOpen(next);
74
+ onOpenChange?.(next);
75
+ }, [controlled, disabled, onOpenChange, open]);
76
+
77
+ return (
78
+ <View
79
+ {...rest}
80
+ ref={setRootRef}
81
+ style={[
82
+ styles.root,
83
+ { borderRadius: desktop ? 24 : 20 },
84
+ error && styles.error,
85
+ disabled && styles.disabled,
86
+ style,
87
+ ]}
88
+ >
89
+ <Pressable
90
+ accessibilityLabel={accessibilityLabel ?? title}
91
+ accessibilityRole="button"
92
+ accessibilityState={{ disabled, expanded: open }}
93
+ disabled={disabled}
94
+ onPress={toggle}
95
+ style={[styles.header, { paddingHorizontal: horizontalPadding }]}
96
+ >
97
+ <Text style={[styles.title, desktop ? styles.titleDesktop : styles.titleMobile]}>
98
+ {title}
99
+ </Text>
100
+ <View style={[styles.chevron, open && styles.chevronOpen]}>
101
+ <ChevronDownIcon color={open ? colors.primary : colors.grey400} size={CHEVRON_SIZE} />
102
+ </View>
103
+ </Pressable>
104
+ {open ? (
105
+ <View style={[styles.content, { paddingHorizontal: horizontalPadding }, contentStyle]}>
106
+ {children}
107
+ </View>
108
+ ) : null}
109
+ </View>
110
+ );
111
+ });
112
+
113
+ const styles = StyleSheet.create({
114
+ root: {
115
+ alignSelf: "stretch",
116
+ backgroundColor: colors.whiteAlpha1,
117
+ borderColor: "transparent",
118
+ borderWidth: 1,
119
+ overflow: "hidden",
120
+ width: "100%",
121
+ },
122
+ header: {
123
+ alignItems: "center",
124
+ flexDirection: "row",
125
+ gap: 16,
126
+ minHeight: 64,
127
+ paddingVertical: 16,
128
+ width: "100%",
129
+ },
130
+ title: {
131
+ color: colors.white,
132
+ flex: 1,
133
+ fontFamily: fonts.sans,
134
+ fontSize: 14,
135
+ letterSpacing: 0,
136
+ lineHeight: 18,
137
+ minWidth: 0,
138
+ },
139
+ titleMobile: {
140
+ fontWeight: "500",
141
+ },
142
+ titleDesktop: {
143
+ fontWeight: "600",
144
+ },
145
+ chevron: {
146
+ alignItems: "center",
147
+ flexShrink: 0,
148
+ height: CHEVRON_SIZE,
149
+ justifyContent: "center",
150
+ transform: [{ rotate: "0deg" }],
151
+ width: CHEVRON_SIZE,
152
+ },
153
+ chevronOpen: {
154
+ transform: [{ rotate: "180deg" }],
155
+ },
156
+ content: {
157
+ paddingBottom: 16,
158
+ },
159
+ error: {
160
+ borderColor: colors.redMuted,
161
+ },
162
+ disabled: {
163
+ opacity: 0.6,
164
+ },
165
+ });
@@ -0,0 +1,2 @@
1
+ export { Accordion } from "./Accordion";
2
+ export type { AccordionProps } from "./Accordion";
@@ -0,0 +1,80 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import { SearchIcon } from "../../icons";
4
+ import { AutocompleteInput, type AutocompleteOption } from "./AutocompleteInput";
5
+
6
+ const locations: AutocompleteOption[] = [
7
+ { value: "yerevan", label: "Yerevan", description: "Armenia" },
8
+ { value: "gyumri", label: "Gyumri", description: "Shirak, Armenia" },
9
+ { value: "dilijan", label: "Dilijan", description: "Tavush, Armenia" },
10
+ ];
11
+
12
+ const meta = {
13
+ title: "Components/AutocompleteInput",
14
+ component: AutocompleteInput,
15
+ parameters: { backgrounds: { default: "black" } },
16
+ } satisfies Meta<typeof AutocompleteInput>;
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ function ControlledExample() {
22
+ const [value, setValue] = useState("");
23
+ const [options, setOptions] = useState(locations);
24
+ return (
25
+ <AutocompleteInput
26
+ label="Address"
27
+ leftIcon={SearchIcon}
28
+ options={options}
29
+ placeholder="Search address..."
30
+ showHint={false}
31
+ showLeftIcon
32
+ value={value}
33
+ onChangeText={(query) => {
34
+ setValue(query);
35
+ setOptions(
36
+ locations.filter((option) =>
37
+ option.label.toLowerCase().includes(query.trim().toLowerCase()),
38
+ ),
39
+ );
40
+ }}
41
+ onOptionSelect={(option) => setValue(option.label)}
42
+ />
43
+ );
44
+ }
45
+
46
+ export const Default: Story = {
47
+ args: {
48
+ options: locations,
49
+ value: "",
50
+ onChangeText: () => undefined,
51
+ onOptionSelect: () => undefined,
52
+ },
53
+ render: () => <ControlledExample />,
54
+ };
55
+
56
+ export const Loading: Story = {
57
+ args: {
58
+ defaultOpen: true,
59
+ label: "Address",
60
+ loading: true,
61
+ options: [],
62
+ placeholder: "Search address...",
63
+ value: "Yere",
64
+ onChangeText: () => undefined,
65
+ onOptionSelect: () => undefined,
66
+ },
67
+ };
68
+
69
+ export const Empty: Story = {
70
+ args: {
71
+ defaultOpen: true,
72
+ emptyText: "No addresses found",
73
+ label: "Address",
74
+ options: [],
75
+ placeholder: "Search address...",
76
+ value: "Unknown",
77
+ onChangeText: () => undefined,
78
+ onOptionSelect: () => undefined,
79
+ },
80
+ };
@@ -0,0 +1,313 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ type ComponentRef,
9
+ type ReactNode,
10
+ } from "react";
11
+ import {
12
+ ActivityIndicator,
13
+ Platform,
14
+ Pressable,
15
+ ScrollView,
16
+ StyleSheet,
17
+ Text,
18
+ View,
19
+ type StyleProp,
20
+ type ViewStyle,
21
+ } from "react-native";
22
+ import { Input, type InputProps } from "../Input";
23
+ import { colors } from "../../theme";
24
+ import { mergeRefs } from "../../utils/mergeRefs";
25
+ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
26
+
27
+ export interface AutocompleteOption {
28
+ value: string;
29
+ label: string;
30
+ description?: string;
31
+ }
32
+
33
+ export interface AutocompleteInputProps extends Omit<
34
+ InputProps,
35
+ "value" | "onChangeText" | "onSubmitEditing"
36
+ > {
37
+ value: string;
38
+ options: AutocompleteOption[];
39
+ onChangeText: (value: string) => void;
40
+ onOptionSelect: (option: AutocompleteOption) => void;
41
+ onSubmit?: (firstOption?: AutocompleteOption) => void;
42
+ open?: boolean;
43
+ defaultOpen?: boolean;
44
+ onOpenChange?: (open: boolean) => void;
45
+ loading?: boolean;
46
+ loadingText?: string;
47
+ emptyText?: string;
48
+ showEmpty?: boolean;
49
+ renderOption?: (option: AutocompleteOption, highlighted: boolean) => ReactNode;
50
+ containerStyle?: StyleProp<ViewStyle>;
51
+ menuStyle?: StyleProp<ViewStyle>;
52
+ containerClassName?: string;
53
+ menuClassName?: string;
54
+ }
55
+
56
+ const MENU_MAX_HEIGHT = 248;
57
+ const BLUR_CLOSE_DELAY_MS = 120;
58
+ const AUTOCOMPLETE_ROOT_Z_INDEX = 1100;
59
+ const AUTOCOMPLETE_MENU_Z_INDEX = 1101;
60
+
61
+ export const AutocompleteInput = forwardRef<ComponentRef<typeof View>, AutocompleteInputProps>(
62
+ function AutocompleteInput(
63
+ {
64
+ value,
65
+ options,
66
+ onChangeText,
67
+ onOptionSelect,
68
+ onSubmit,
69
+ open: openProp,
70
+ defaultOpen = false,
71
+ onOpenChange,
72
+ loading = false,
73
+ loadingText = "Loading...",
74
+ emptyText = "No results",
75
+ showEmpty = true,
76
+ renderOption,
77
+ containerStyle,
78
+ menuStyle,
79
+ containerClassName,
80
+ menuClassName,
81
+ disabled = false,
82
+ onFocus,
83
+ onBlur,
84
+ ...inputProps
85
+ },
86
+ forwardedRef,
87
+ ) {
88
+ const rootRef = useRef<ComponentRef<typeof View>>(null);
89
+ const menuRef = useRef<ComponentRef<typeof View>>(null);
90
+ const blurTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
91
+ const setRootRef = useMemo(() => mergeRefs(rootRef, forwardedRef), [forwardedRef]);
92
+ const controlled = typeof openProp === "boolean";
93
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
94
+ const [highlightedIndex, setHighlightedIndex] = useState(0);
95
+ const open = controlled ? openProp : uncontrolledOpen;
96
+
97
+ useApplyWebClassName(rootRef, containerClassName);
98
+ useApplyWebClassName(menuRef, menuClassName, open);
99
+
100
+ const setOpen = useCallback(
101
+ (nextOpen: boolean) => {
102
+ if (!controlled) setUncontrolledOpen(nextOpen);
103
+ onOpenChange?.(nextOpen);
104
+ if (!nextOpen) setHighlightedIndex(0);
105
+ },
106
+ [controlled, onOpenChange],
107
+ );
108
+
109
+ useEffect(() => {
110
+ if (highlightedIndex < options.length) return;
111
+ setHighlightedIndex(Math.max(0, options.length - 1));
112
+ }, [highlightedIndex, options.length]);
113
+
114
+ useEffect(() => {
115
+ if (Platform.OS !== "web" || !open) return;
116
+ const closeOnOutsidePress = (event: MouseEvent | TouchEvent) => {
117
+ const root = rootRef.current as unknown as { contains?: (node: Node) => boolean } | null;
118
+ if (event.target instanceof Node && root?.contains && !root.contains(event.target)) {
119
+ setOpen(false);
120
+ }
121
+ };
122
+ document.addEventListener("mousedown", closeOnOutsidePress);
123
+ document.addEventListener("touchstart", closeOnOutsidePress);
124
+ return () => {
125
+ document.removeEventListener("mousedown", closeOnOutsidePress);
126
+ document.removeEventListener("touchstart", closeOnOutsidePress);
127
+ };
128
+ }, [open, setOpen]);
129
+
130
+ useEffect(
131
+ () => () => {
132
+ if (blurTimerRef.current) clearTimeout(blurTimerRef.current);
133
+ },
134
+ [],
135
+ );
136
+
137
+ const selectOption = (option: AutocompleteOption) => {
138
+ if (disabled) return;
139
+ if (blurTimerRef.current) clearTimeout(blurTimerRef.current);
140
+ onOptionSelect(option);
141
+ setOpen(false);
142
+ };
143
+
144
+ const submit = () => {
145
+ const highlighted = options[highlightedIndex] ?? options[0];
146
+ if (open && highlighted) {
147
+ selectOption(highlighted);
148
+ return;
149
+ }
150
+ onSubmit?.(options[0]);
151
+ };
152
+
153
+ const showMenu = open && !disabled && (loading || options.length > 0 || showEmpty);
154
+
155
+ return (
156
+ <View
157
+ ref={setRootRef}
158
+ style={[styles.root, open && styles.rootOpen, containerStyle]}
159
+ accessibilityState={{ disabled, expanded: open }}
160
+ >
161
+ <Input
162
+ {...inputProps}
163
+ disabled={disabled}
164
+ value={value}
165
+ onChangeText={(nextValue) => {
166
+ onChangeText(nextValue);
167
+ setHighlightedIndex(0);
168
+ setOpen(true);
169
+ }}
170
+ onFocus={(event) => {
171
+ if (blurTimerRef.current) clearTimeout(blurTimerRef.current);
172
+ setOpen(true);
173
+ onFocus?.(event);
174
+ }}
175
+ onBlur={(event) => {
176
+ blurTimerRef.current = setTimeout(() => setOpen(false), BLUR_CLOSE_DELAY_MS);
177
+ onBlur?.(event);
178
+ }}
179
+ onKeyPress={(event) => {
180
+ const key = event.nativeEvent.key;
181
+ if (key === "ArrowDown" && options.length > 0) {
182
+ setHighlightedIndex((current) => Math.min(current + 1, options.length - 1));
183
+ } else if (key === "ArrowUp" && options.length > 0) {
184
+ setHighlightedIndex((current) => Math.max(current - 1, 0));
185
+ } else if (key === "Escape") {
186
+ setOpen(false);
187
+ }
188
+ inputProps.onKeyPress?.(event);
189
+ }}
190
+ onSubmitEditing={submit}
191
+ />
192
+
193
+ {showMenu ? (
194
+ <View ref={menuRef} style={[styles.menu, menuStyle]}>
195
+ {loading ? (
196
+ <View style={styles.statusRow}>
197
+ <ActivityIndicator color={colors.primary} size="small" />
198
+ <Text style={styles.statusText}>{loadingText}</Text>
199
+ </View>
200
+ ) : options.length === 0 ? (
201
+ <View style={styles.statusRow}>
202
+ <Text style={styles.statusText}>{emptyText}</Text>
203
+ </View>
204
+ ) : (
205
+ <ScrollView
206
+ keyboardShouldPersistTaps="handled"
207
+ nestedScrollEnabled
208
+ showsVerticalScrollIndicator
209
+ style={styles.list}
210
+ >
211
+ {options.map((option, index) => {
212
+ const highlighted = index === highlightedIndex;
213
+ return (
214
+ <Pressable
215
+ accessibilityRole="button"
216
+ key={option.value}
217
+ onHoverIn={() => setHighlightedIndex(index)}
218
+ onPress={() => selectOption(option)}
219
+ style={[styles.option, highlighted && styles.optionHighlighted]}
220
+ >
221
+ {renderOption ? (
222
+ renderOption(option, highlighted)
223
+ ) : (
224
+ <>
225
+ <Text numberOfLines={1} style={styles.optionLabel}>
226
+ {option.label}
227
+ </Text>
228
+ {option.description ? (
229
+ <Text numberOfLines={1} style={styles.optionDescription}>
230
+ {option.description}
231
+ </Text>
232
+ ) : null}
233
+ </>
234
+ )}
235
+ </Pressable>
236
+ );
237
+ })}
238
+ </ScrollView>
239
+ )}
240
+ </View>
241
+ ) : null}
242
+ </View>
243
+ );
244
+ },
245
+ );
246
+
247
+ const styles = StyleSheet.create({
248
+ root: {
249
+ position: "relative",
250
+ width: 308,
251
+ },
252
+ rootOpen: {
253
+ zIndex: AUTOCOMPLETE_ROOT_Z_INDEX,
254
+ },
255
+ menu: {
256
+ position: "absolute",
257
+ top: "100%",
258
+ left: 0,
259
+ right: 0,
260
+ zIndex: AUTOCOMPLETE_MENU_Z_INDEX,
261
+ marginTop: 4,
262
+ padding: 4,
263
+ overflow: "hidden",
264
+ borderWidth: 1,
265
+ borderColor: colors.grey700,
266
+ borderRadius: 16,
267
+ backgroundColor: colors.grey700,
268
+ shadowColor: colors.black,
269
+ shadowOffset: { width: 0, height: 8 },
270
+ shadowOpacity: 0.4,
271
+ shadowRadius: 18,
272
+ elevation: 12,
273
+ },
274
+ list: {
275
+ maxHeight: MENU_MAX_HEIGHT,
276
+ },
277
+ option: {
278
+ minHeight: 48,
279
+ justifyContent: "center",
280
+ gap: 2,
281
+ paddingHorizontal: 12,
282
+ paddingVertical: 8,
283
+ borderRadius: 12,
284
+ },
285
+ optionHighlighted: {
286
+ backgroundColor: colors.grey600,
287
+ },
288
+ optionLabel: {
289
+ color: colors.white,
290
+ fontSize: 12,
291
+ lineHeight: 18,
292
+ fontWeight: "600",
293
+ },
294
+ optionDescription: {
295
+ color: colors.grey100,
296
+ fontSize: 11,
297
+ lineHeight: 16,
298
+ },
299
+ statusRow: {
300
+ minHeight: 48,
301
+ flexDirection: "row",
302
+ alignItems: "center",
303
+ justifyContent: "center",
304
+ gap: 8,
305
+ paddingHorizontal: 12,
306
+ paddingVertical: 8,
307
+ },
308
+ statusText: {
309
+ color: colors.grey100,
310
+ fontSize: 12,
311
+ lineHeight: 18,
312
+ },
313
+ });
@@ -0,0 +1,2 @@
1
+ export { AutocompleteInput } from "./AutocompleteInput";
2
+ export type { AutocompleteInputProps, AutocompleteOption } from "./AutocompleteInput";