Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {useEffect, useRef} from 'react'
import PropTypes from 'prop-types'
import {
Box,
Flex,
Text,
IconButton,
VStack,
HStack,
Spinner
} from '@salesforce/retail-react-app/app/components/shared/ui'
import {CloseIcon} from '@salesforce/retail-react-app/app/components/icons'

/**
* Address Suggestion Dropdown Component
* Displays Google-powered address suggestions in a dropdown format
*/
const AddressSuggestionDropdown = ({
suggestions = [],
isLoading = false,
onClose,
onSelectSuggestion,
isVisible = false,
position = 'absolute'
}) => {
const dropdownRef = useRef(null)

// Handle click outside
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
onClose()
}
}

if (isVisible) {
document.addEventListener('mousedown', handleClickOutside)
}

return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [isVisible, onClose])

if (!isVisible || suggestions.length === 0) {
return null
}

if (isLoading) {
return (
<Box
position="absolute"
top="100%"
left={0}
right={0}
bg="white"
border="1px solid"
borderColor="gray.200"
borderRadius="md"
boxShadow="md"
zIndex={1000}
p={4}
>
<Flex align="center" justify="center">
<Spinner size="sm" mr={2} />
<Text>Loading suggestions...</Text>
</Flex>
</Box>
)
}

return (
<Box
ref={dropdownRef}
data-testid="address-suggestion-dropdown"
position={position}
top="100%"
left={0}
right={0}
zIndex={1000}
bg="white"
border="1px solid"
borderColor="gray.200"
borderRadius="md"
boxShadow="md"
mt={1}
maxH="300px"
overflowY="auto"
>
<Flex
px={4}
py={2}
borderBottom="1px solid"
borderColor="gray.200"
justifyContent="space-between"
alignItems="center"
>
<Text fontSize="sm" fontWeight="medium" color="gray.600">
Suggested
</Text>
<IconButton
aria-label="Close suggestions"
icon={<CloseIcon />}
variant="ghost"
size="sm"
onClick={onClose}
/>
</Flex>
{suggestions.map((suggestion, index) => (
<Box
key={index}
px={4}
py={2}
cursor="pointer"
_hover={{bg: 'gray.50'}}
onClick={() => onSelectSuggestion(suggestion)}
role="button"
tabIndex={0}
onKeyPress={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
onSelectSuggestion(suggestion)
}
}}
>
<Flex alignItems="center" gap={2}>
{/* Location Marker */}
<Box position="relative" w={4} h={4}>
<Box
position="absolute"
top={0}
left={0}
w={4}
h={4}
bg="blue.500"
borderRadius="full"
opacity={0.2}
/>
<Box
position="absolute"
top={1}
left={1}
w={2}
h={2}
bg="blue.500"
borderRadius="full"
/>
</Box>

{/* Address Text */}
<Box flex={1}>
<Text fontSize="sm" noOfLines={1}>
{suggestion.mainText}
</Text>
{suggestion.secondaryText && (
<Text fontSize="xs" color="gray.500" noOfLines={1}>
{suggestion.secondaryText}
</Text>
)}
</Box>
</Flex>
</Box>
))}
</Box>
)
}

AddressSuggestionDropdown.propTypes = {
/** Array of address suggestions to display */
suggestions: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
address: PropTypes.string,
mainText: PropTypes.string,
secondaryText: PropTypes.string
})
),

/** Whether the dropdown should be visible */
isVisible: PropTypes.bool,

/** Callback when close button is clicked */
onClose: PropTypes.func.isRequired,

/** Callback when a suggestion is selected */
onSelectSuggestion: PropTypes.func.isRequired,

/** CSS position property for the dropdown */
position: PropTypes.oneOf(['absolute', 'relative', 'fixed']),

/** Whether the dropdown is loading */
isLoading: PropTypes.bool
}

export default AddressSuggestionDropdown
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import {
Grid,
GridItem,
SimpleGrid,
Stack
Stack,
Box
} from '@salesforce/retail-react-app/app/components/shared/ui'
import useAddressFields from '@salesforce/retail-react-app/app/components/forms/useAddressFields'
import Field from '@salesforce/retail-react-app/app/components/field'
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
import {MESSAGE_PROPTYPE} from '@salesforce/retail-react-app/app/utils/locale'
import AddressSuggestionDropdown from '@salesforce/retail-react-app/app/components/forms/AddressSuggestionDropdown'

const defaultFormTitleAriaLabel = defineMessage({
defaultMessage: 'Address Form',
Expand Down Expand Up @@ -51,7 +53,21 @@ const AddressFields = ({
</SimpleGrid>
<Field {...fields.phone} />
<Field {...fields.countryCode} />

{/* Address field with autocomplete dropdown */}
<Box position="relative">
<Field {...fields.address1} />

{/* Address suggestion dropdown */}
<AddressSuggestionDropdown
suggestions={fields.address1.autocomplete.suggestions}
isVisible={fields.address1.autocomplete.showDropdown && !fields.address1.autocomplete.isDismissed}
onClose={fields.address1.autocomplete.onClose}
onSelectSuggestion={fields.address1.autocomplete.onSelectSuggestion}
position="absolute"
/>
</Box>

<Field {...fields.city} />
<Grid templateColumns="repeat(8, 1fr)" gap={5}>
<GridItem colSpan={[4, 4, 4]}>
Expand Down
Loading