diff --git a/src/components/ListingCard/ListingCard.js b/src/components/ListingCard/ListingCard.js index 571632ea..08d79d3d 100644 --- a/src/components/ListingCard/ListingCard.js +++ b/src/components/ListingCard/ListingCard.js @@ -50,7 +50,7 @@ const formatTitle = (title, maxLength) => { }; export const ListingCardComponent = props => { - const { className, rootClassName, intl, listing } = props; + const { className, rootClassName, intl, listing, setActiveListing } = props; const classes = classNames(rootClassName || css.root, className); const currentListing = ensureListing(listing); const id = currentListing.id.uuid; @@ -65,7 +65,11 @@ export const ListingCardComponent = props => { return ( -
+
setActiveListing(currentListing.id)} + onMouseLeave={() => setActiveListing(null)} + >
-
+
{ */ const MapWithGoogleMap = withGoogleMap(props => { const { + activeListingId, center, infoCardOpen, isOpenOnModal, @@ -103,6 +104,10 @@ const MapWithGoogleMap = withGoogleMap(props => { const listingArraysInLocations = reducedToArray(groupedByCoordinates(listings)); const priceLabels = listingArraysInLocations.reverse().map(listingArr => { + const isActive = activeListingId + ? !!listingArr.find(l => activeListingId.uuid === l.id.uuid) + : false; + // If location contains only one listing, print price label if (listingArr.length === 1) { const listing = listingArr[0]; @@ -114,6 +119,7 @@ const MapWithGoogleMap = withGoogleMap(props => { } return ( { } return ( { class SearchMapGroupLabel extends Component { shouldComponentUpdate(nextProps) { - return nextProps.listings.length > this.props.listings.length; + const hasSameAmountOfListings = nextProps.listings.length === this.props.listings.length; + const hasSameActiveStatus = this.props.isActive === nextProps.isActive; + + return !(hasSameAmountOfListings && hasSameActiveStatus); } render() { - const { className, rootClassName, listings, onListingClicked } = this.props; + const { className, rootClassName, listings, onListingClicked, isActive } = this.props; const firstListing = ensureListing(listings[0]); const geolocation = firstListing.attributes.geolocation; // Explicit type change to object literal for Google OverlayViews (geolocation is SDK type) const latLngLiteral = { lat: geolocation.lat, lng: geolocation.lng }; const classes = classNames(rootClassName || css.root, className); + const countLabelClasses = classNames(css.details, { [css.detailsActive]: isActive }); + const caretClasses = classNames(css.caret, { [css.caretActive]: isActive }); return ( ); diff --git a/src/components/SearchMapPriceLabel/SearchMapPriceLabel.css b/src/components/SearchMapPriceLabel/SearchMapPriceLabel.css index b01cad5e..5c272b22 100644 --- a/src/components/SearchMapPriceLabel/SearchMapPriceLabel.css +++ b/src/components/SearchMapPriceLabel/SearchMapPriceLabel.css @@ -6,6 +6,19 @@ height: auto; border: 0; padding: 0; + + &:hover { + & .priceLabel, + & .caret { + cursor: pointer; + + /* Same as active */ + background-color: var(--marketplaceColor); + border-color: var(--marketplaceColor); + color: var(--matterColorLight); + box-shadow: var(--boxShadowPopup); + } + } } .priceLabel { @@ -36,12 +49,6 @@ margin-bottom: 0; transition: var(--transitionStyleButton); - &:hover { - color: var(--marketplaceColor); - cursor: pointer; - box-shadow: var(--boxShadowPopup); - } - /* Overwrite dimensions from font styles */ @media (--viewportMedium) { margin-top: 0; @@ -49,6 +56,14 @@ } } +.priceLabelActive { + /* Same as hover */ + background-color: var(--marketplaceColor); + border-color: var(--marketplaceColor); + color: var(--matterColorLight); + box-shadow: var(--boxShadowPopup); +} + .caretShadow { /* Caret / arrow dimensions and position */ width: 6px; @@ -73,6 +88,8 @@ margin-left: -3px; transform: rotate(45deg); + transition: var(--transitionStyleButton); + /* Caret should have same bg-color and border as label */ background-color: var(--matterColorLight); border-right-style: solid; @@ -82,3 +99,9 @@ border-bottom-color: var(--matterColorNegative); border-bottom-width: 1px; } + +.caretActive { + /* Same as hover */ + background-color: var(--marketplaceColor); + border-color: var(--marketplaceColor); +} diff --git a/src/components/SearchMapPriceLabel/SearchMapPriceLabel.js b/src/components/SearchMapPriceLabel/SearchMapPriceLabel.js index e23036cc..b0877391 100644 --- a/src/components/SearchMapPriceLabel/SearchMapPriceLabel.js +++ b/src/components/SearchMapPriceLabel/SearchMapPriceLabel.js @@ -22,11 +22,13 @@ class SearchMapPriceLabel extends Component { const nextListing = ensureListing(nextProps.listing); const isSameListing = currentListing.id.uuid === nextListing.id.uuid; const hasSamePrice = currentListing.attributes.price === nextListing.attributes.price; - return !(isSameListing && hasSamePrice); + const hasSameActiveStatus = this.props.isActive === nextProps.isActive; + + return !(isSameListing && hasSamePrice && hasSameActiveStatus); } render() { - const { className, rootClassName, intl, listing, onListingClicked } = this.props; + const { className, rootClassName, intl, listing, onListingClicked, isActive } = this.props; const currentListing = ensureListing(listing); const { geolocation, price } = currentListing.attributes; @@ -37,6 +39,8 @@ class SearchMapPriceLabel extends Component { // Explicit type change to object literal for Google OverlayViews (geolocation is SDK type) const latLngLiteral = { lat: geolocation.lat, lng: geolocation.lng }; const classes = classNames(rootClassName || css.root, className); + const priceLabelClasses = classNames(css.priceLabel, { [css.priceLabelActive]: isActive }); + const caretClasses = classNames(css.caret, { [css.caretActive]: isActive }); return ( ); diff --git a/src/components/SearchResultsPanel/SearchResultsPanel.js b/src/components/SearchResultsPanel/SearchResultsPanel.js index 873a00c2..fde4b18b 100644 --- a/src/components/SearchResultsPanel/SearchResultsPanel.js +++ b/src/components/SearchResultsPanel/SearchResultsPanel.js @@ -6,7 +6,7 @@ import { ListingCard, PaginationLinks } from '../../components'; import css from './SearchResultsPanel.css'; const SearchResultsPanel = props => { - const { className, rootClassName, listings, pagination, search } = props; + const { className, rootClassName, listings, pagination, search, setActiveListing } = props; const classes = classNames(rootClassName || css.root, className); const paginationLinks = @@ -22,7 +22,14 @@ const SearchResultsPanel = props => { return (
- {listings.map(l => )} + {listings.map(l => ( + + ))} {props.children}
{paginationLinks} diff --git a/src/containers/SearchPage/SearchPage.duck.js b/src/containers/SearchPage/SearchPage.duck.js index 2af47621..3a0c8f1c 100644 --- a/src/containers/SearchPage/SearchPage.duck.js +++ b/src/containers/SearchPage/SearchPage.duck.js @@ -12,6 +12,8 @@ export const SEARCH_MAP_LISTINGS_REQUEST = 'app/SearchPage/SEARCH_MAP_LISTINGS_R export const SEARCH_MAP_LISTINGS_SUCCESS = 'app/SearchPage/SEARCH_MAP_LISTINGS_SUCCESS'; export const SEARCH_MAP_LISTINGS_ERROR = 'app/SearchPage/SEARCH_MAP_LISTINGS_ERROR'; +export const SEARCH_MAP_SET_ACTIVE_LISTING = 'app/SearchPage/SEARCH_MAP_SET_ACTIVE_LISTING'; + // ================ Reducer ================ // const initialState = { @@ -70,6 +72,11 @@ const listingPageReducer = (state = initialState, action = {}) => { console.error(payload); return { ...state, searchMapListingsError: payload }; + case SEARCH_MAP_SET_ACTIVE_LISTING: + return { + ...state, + activeListingId: payload, + }; default: return state; } @@ -130,6 +137,11 @@ export const searchListings = searchParams => (dispatch, getState, sdk) => { }); }; +export const setActiveListing = listingId => ({ + type: SEARCH_MAP_SET_ACTIVE_LISTING, + payload: listingId, +}); + export const searchMapListings = searchParams => (dispatch, getState, sdk) => { dispatch(searchMapListingsRequest(searchParams)); diff --git a/src/containers/SearchPage/SearchPage.js b/src/containers/SearchPage/SearchPage.js index 9134a95b..6d1bacf1 100644 --- a/src/containers/SearchPage/SearchPage.js +++ b/src/containers/SearchPage/SearchPage.js @@ -29,7 +29,7 @@ import { SearchFiltersPanel, } from '../../components'; import { TopbarContainer } from '../../containers'; -import { searchListings, searchMapListings } from './SearchPage.duck'; +import { searchListings, searchMapListings, setActiveListing } from './SearchPage.duck'; import css from './SearchPage.css'; @@ -260,6 +260,8 @@ export class SearchPageComponent extends Component { searchParams, categories, amenities, + activeListingId, + onActivateListing, } = this.props; // eslint-disable-next-line no-unused-vars const { mapSearch, page, ...searchInURL } = parse(location.search, { @@ -292,6 +294,7 @@ export class SearchPageComponent extends Component { const searchMap = (
); @@ -475,6 +479,7 @@ SearchPageComponent.defaultProps = { tab: 'listings', categories: config.custom.categories, amenities: config.custom.amenities, + activeListingId: null, }; const { array, bool, func, oneOf, object, shape, string } = PropTypes; @@ -513,6 +518,7 @@ const mapStateToProps = state => { searchListingsError, searchParams, searchMapListingIds, + activeListingId, } = state.SearchPage; const pageListings = getListingsById(state, currentPageResultIds); const mapListings = getListingsById( @@ -528,6 +534,7 @@ const mapStateToProps = state => { searchInProgress, searchListingsError, searchParams, + activeListingId, }; }; @@ -535,6 +542,7 @@ const mapDispatchToProps = dispatch => ({ onManageDisableScrolling: (componentId, disableScrolling) => dispatch(manageDisableScrolling(componentId, disableScrolling)), onSearchMapListings: searchParams => dispatch(searchMapListings(searchParams)), + onActivateListing: listingId => dispatch(setActiveListing(listingId)), }); // Note: it is important that the withRouter HOC is **outside** the diff --git a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap index b72b4352..2c6d9c8f 100644 --- a/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap +++ b/src/containers/SearchPage/__snapshots__/SearchPage.test.js.snap @@ -119,6 +119,7 @@ exports[`SearchPageComponent matches snapshot 1`] = ` >