Skip to content

Commit fd6b0f2

Browse files
harshini-mageshdannyphan2000
authored andcommitted
@W-18762700: Implemented address autocomplete dropdown using mock addresses (#2614)
This PR implements Address Autocomplete functionality for checkout in the PWA Kit storefront.The implementation includes a mock address service that simulates the Google Places API, which can then be replaced with the actual API integration in the future.
1 parent 96b7bda commit fd6b0f2

File tree

7 files changed

+1492
-3
lines changed

7 files changed

+1492
-3
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright (c) 2021, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import React, {useEffect, useRef} from 'react'
8+
import PropTypes from 'prop-types'
9+
import {
10+
Box,
11+
Flex,
12+
Text,
13+
IconButton,
14+
VStack,
15+
HStack,
16+
Spinner
17+
} from '@salesforce/retail-react-app/app/components/shared/ui'
18+
import {CloseIcon} from '@salesforce/retail-react-app/app/components/icons'
19+
20+
/**
21+
* Address Suggestion Dropdown Component
22+
* Displays Google-powered address suggestions in a dropdown format
23+
*/
24+
const AddressSuggestionDropdown = ({
25+
suggestions = [],
26+
isLoading = false,
27+
onClose,
28+
onSelectSuggestion,
29+
isVisible = false,
30+
position = 'absolute'
31+
}) => {
32+
const dropdownRef = useRef(null)
33+
34+
// Handle click outside
35+
useEffect(() => {
36+
const handleClickOutside = (event) => {
37+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
38+
onClose()
39+
}
40+
}
41+
42+
if (isVisible) {
43+
document.addEventListener('mousedown', handleClickOutside)
44+
}
45+
46+
return () => {
47+
document.removeEventListener('mousedown', handleClickOutside)
48+
}
49+
}, [isVisible, onClose])
50+
51+
if (!isVisible || suggestions.length === 0) {
52+
return null
53+
}
54+
55+
if (isLoading) {
56+
return (
57+
<Box
58+
position="absolute"
59+
top="100%"
60+
left={0}
61+
right={0}
62+
bg="white"
63+
border="1px solid"
64+
borderColor="gray.200"
65+
borderRadius="md"
66+
boxShadow="md"
67+
zIndex={1000}
68+
p={4}
69+
>
70+
<Flex align="center" justify="center">
71+
<Spinner size="sm" mr={2} />
72+
<Text>Loading suggestions...</Text>
73+
</Flex>
74+
</Box>
75+
)
76+
}
77+
78+
return (
79+
<Box
80+
ref={dropdownRef}
81+
data-testid="address-suggestion-dropdown"
82+
position={position}
83+
top="100%"
84+
left={0}
85+
right={0}
86+
zIndex={1000}
87+
bg="white"
88+
border="1px solid"
89+
borderColor="gray.200"
90+
borderRadius="md"
91+
boxShadow="md"
92+
mt={1}
93+
maxH="300px"
94+
overflowY="auto"
95+
>
96+
<Flex
97+
px={4}
98+
py={2}
99+
borderBottom="1px solid"
100+
borderColor="gray.200"
101+
justifyContent="space-between"
102+
alignItems="center"
103+
>
104+
<Text fontSize="sm" fontWeight="medium" color="gray.600">
105+
Suggested
106+
</Text>
107+
<IconButton
108+
aria-label="Close suggestions"
109+
icon={<CloseIcon />}
110+
variant="ghost"
111+
size="sm"
112+
onClick={onClose}
113+
/>
114+
</Flex>
115+
{suggestions.map((suggestion, index) => (
116+
<Box
117+
key={index}
118+
px={4}
119+
py={2}
120+
cursor="pointer"
121+
_hover={{bg: 'gray.50'}}
122+
onClick={() => onSelectSuggestion(suggestion)}
123+
role="button"
124+
tabIndex={0}
125+
onKeyDown={(e) => {
126+
if (e.key === 'Enter' || e.key === ' ') {
127+
onSelectSuggestion(suggestion)
128+
}
129+
}}
130+
>
131+
<Flex alignItems="center" gap={2}>
132+
{/* Location Marker */}
133+
<Box position="relative" w={4} h={4}>
134+
<Box
135+
position="absolute"
136+
top={0}
137+
left={0}
138+
w={4}
139+
h={4}
140+
bg="blue.500"
141+
borderRadius="full"
142+
opacity={0.2}
143+
/>
144+
<Box
145+
position="absolute"
146+
top={1}
147+
left={1}
148+
w={2}
149+
h={2}
150+
bg="blue.500"
151+
borderRadius="full"
152+
/>
153+
</Box>
154+
155+
{/* Address Text */}
156+
<Box flex={1}>
157+
<Text fontSize="sm" noOfLines={1}>
158+
{suggestion.structured_formatting.main_text}
159+
</Text>
160+
{suggestion.structured_formatting.secondary_text && (
161+
<Text fontSize="xs" color="gray.500" noOfLines={1}>
162+
{suggestion.structured_formatting.secondary_text}
163+
</Text>
164+
)}
165+
</Box>
166+
</Flex>
167+
</Box>
168+
))}
169+
</Box>
170+
)
171+
}
172+
173+
AddressSuggestionDropdown.propTypes = {
174+
/** Array of address suggestions to display */
175+
suggestions: PropTypes.arrayOf(
176+
PropTypes.shape({
177+
description: PropTypes.string,
178+
place_id: PropTypes.string,
179+
structured_formatting: PropTypes.shape({
180+
main_text: PropTypes.string,
181+
secondary_text: PropTypes.string
182+
}),
183+
terms: PropTypes.arrayOf(
184+
PropTypes.shape({
185+
offset: PropTypes.number,
186+
value: PropTypes.string
187+
})
188+
),
189+
types: PropTypes.arrayOf(PropTypes.string)
190+
})
191+
),
192+
193+
/** Whether the dropdown should be visible */
194+
isVisible: PropTypes.bool,
195+
196+
/** Callback when close button is clicked */
197+
onClose: PropTypes.func.isRequired,
198+
199+
/** Callback when a suggestion is selected */
200+
onSelectSuggestion: PropTypes.func.isRequired,
201+
202+
/** CSS position property for the dropdown */
203+
position: PropTypes.oneOf(['absolute', 'relative', 'fixed']),
204+
205+
/** Whether the dropdown is loading */
206+
isLoading: PropTypes.bool
207+
}
208+
209+
export default AddressSuggestionDropdown

0 commit comments

Comments
 (0)