diff --git a/app/icon.svg b/app/icon.svg index 70789df0..e0cf131e 100644 --- a/app/icon.svg +++ b/app/icon.svg @@ -1,3 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/listings/content.tsx b/app/listings/content.tsx index 766d3c45..16219950 100644 --- a/app/listings/content.tsx +++ b/app/listings/content.tsx @@ -11,7 +11,6 @@ interface ListingsContentInterface { export default function ListingsContent(props: ListingsContentInterface) { return (
- {/* TODO: Display listings as pins */}
diff --git a/app/listings/map.tsx b/app/listings/map.tsx index 2a86805f..2c544b87 100644 --- a/app/listings/map.tsx +++ b/app/listings/map.tsx @@ -1,8 +1,11 @@ "use client" +import ButtonIconClose from "@/components/button-icons/close" +import CardListing from "@/components/card-listing" import { NumberUtils } from "@/lib/commons/number_utils" import { CURRENCY_FORMATTER } from "@/lib/formatter/currency" -import { Feature, View } from "ol" +import { Feature, Overlay, View } from "ol" +import { easeIn } from "ol/easing" import Point from "ol/geom/Point" import TileLayer from "ol/layer/Tile" import VectorLayer from "ol/layer/Vector" @@ -13,7 +16,7 @@ import VectorSource from "ol/source/Vector" import { Fill, Stroke, Style, Text } from "ol/style" import CircleStyle from "ol/style/Circle" import { StyleLike } from "ol/style/Style" -import { useEffect } from "react" +import { useEffect, useState } from "react" import { Listing } from "./types" const ICON_STYLE = new Style({ @@ -114,6 +117,7 @@ function createMapFeatures(listings: Listing[]): MapFeatures { } export function ListingsMap(props: ListingsMapInterface) { + const [activeListing, setActiveListing] = useState(); const mapFeatures = createMapFeatures(props.listings); useEffect(() => { @@ -142,6 +146,7 @@ export function ListingsMap(props: ListingsMapInterface) { }), }) + // Change features display style based on the map's zoom level let featureDisplayState: 'icon' | 'text' = 'icon'; const MAP_ZOOM_TEXT = 15; map.on('moveend', (e) => { @@ -157,13 +162,14 @@ export function ListingsMap(props: ListingsMapInterface) { } }) + // Change feature display style to active when hovered upon let activeIndex: number = -1; let previousStyle: StyleLike | undefined; map.on('pointermove', (e) => { // Hover (revert style) if (activeIndex >= 0) { - // Use icons features if zoom level is big - // Use text features if zoom level is small + // Uses icon features if zoom level is big + // Uses text features if zoom level is small let activeFeature = mapFeatures.iconFeatures.at(activeIndex); if (featureDisplayState === 'text') { activeFeature = mapFeatures.textFeatures.at(activeIndex); @@ -184,10 +190,6 @@ export function ListingsMap(props: ListingsMapInterface) { const hoveredFeature = hoveredFeatures.at(hoveredFeatures.length - 1) as Feature; const hoveredIndex = NumberUtils.toNumber(hoveredFeature.getId(), -1) - // TODO: Display popup/banner of listing card - // 1. https://openlayers.org/en/latest/examples/overlay.html (HTML only) - // 2. https://openlayers.org/en/latest/examples/popup.html (Bootstrap) - // 3. https://openlayers.org/en/latest/examples/select-features.html if (hoveredIndex !== activeIndex) { activeIndex = hoveredIndex const activeFeature = mapFeatures.activeFeatures.at(activeIndex); @@ -200,10 +202,61 @@ export function ListingsMap(props: ListingsMapInterface) { }) }) + map.on('click', (e) => { + // Show overlay if a listing feature was clicked + if (activeIndex >= 0) { + // Show content + setActiveListing(props.listings.at(activeIndex)); + + // Set overlay's element and position + const mapOverlay = new Overlay({ + element: document.getElementById('popup') ?? undefined, + autoPan: { + animation: { + duration: 250, + easing: easeIn, + }, + margin: 20, + }, + position: e.coordinate + }); + map.addOverlay(mapOverlay); + } else if (activeIndex < 0) { + setActiveListing(undefined) + } + }) + return () => map.dispose() }, []) return ( -
+
+
+ +
) } \ No newline at end of file diff --git a/components/button-icons/close.tsx b/components/button-icons/close.tsx new file mode 100644 index 00000000..91dc5b57 --- /dev/null +++ b/components/button-icons/close.tsx @@ -0,0 +1,23 @@ +"use client" + +import ButtonIcon from "." +import { iconSize } from "./functions" +import { ButtonIconActualProps } from "./types" + +export default function ButtonIconClose(props: ButtonIconActualProps) { + const size = iconSize(props.size) + + return + + + + +} diff --git a/stories/button-icons/ButtonIconClose.stories.tsx b/stories/button-icons/ButtonIconClose.stories.tsx new file mode 100644 index 00000000..7f477660 --- /dev/null +++ b/stories/button-icons/ButtonIconClose.stories.tsx @@ -0,0 +1,20 @@ +import ButtonIconClose from "@/components/button-icons/close"; +import type { Meta, StoryObj } from "@storybook/react"; + +const meta: Meta = { + title: "Button Icons/Close", + component: ButtonIconClose, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Example: Story = { + args: { + size: 48, + onClick: () => { + console.log("Added to favorites 💗"); + }, + }, +};