Skip to content
Snippets Groups Projects
Commit 10a6ea77 authored by Moritz Stueckler's avatar Moritz Stueckler
Browse files

feat: add poi hover effects, website link

parent 4abc29e7
No related branches found
No related tags found
1 merge request!9Feat/poi hover effects
This diff is collapsed.
...@@ -10,27 +10,47 @@ interface AppProps {} ...@@ -10,27 +10,47 @@ interface AppProps {}
function App({}: AppProps) { function App({}: AppProps) {
const [selectedPoi, setSelectedPoi] = useState<null | PointOfInterest>(null); const [selectedPoi, setSelectedPoi] = useState<null | PointOfInterest>(null);
const [hoveredPoiId, setHoveredPoiId] = useState<null | number>(null);
const handlePoiClick = (id: number) => { const handlePoiClick = (id: number) => {
console.log('Selected', id);
const newPoi = (data as PointOfInterest[]).find((poi) => poi.id === id); const newPoi = (data as PointOfInterest[]).find((poi) => poi.id === id);
newPoi && setSelectedPoi(newPoi); newPoi && setSelectedPoi(newPoi);
}; };
const handlePoiClose = () => { const handlePoiClose = () => {
console.log('Close');
setSelectedPoi(null); setSelectedPoi(null);
}; };
const handlePoiHoverOn = (poiId: number) => {
setHoveredPoiId(poiId);
};
const handlePoiHoverOff = () => {
setHoveredPoiId(null);
};
return ( return (
<div className={'flex h-full'}> <div className={'flex h-full'}>
{selectedPoi ? ( {selectedPoi ? (
<SidebarSingleView style={{ flex: 1 }} value={selectedPoi} onClose={handlePoiClose} /> <SidebarSingleView style={{ flex: 1, minWidth: '250px' }} value={selectedPoi} onClose={handlePoiClose} />
) : ( ) : (
<SidebarListView style={{ flex: 1 }} values={data as PointOfInterest[]} onClick={handlePoiClick} /> <SidebarListView
onMouseEnter={handlePoiHoverOn}
onMouseLeave={handlePoiHoverOff}
style={{ flex: 1, minWidth: '250px' }}
values={data as PointOfInterest[]}
onClick={handlePoiClick}
/>
)} )}
<Map values={data as PointOfInterest[]} onSelect={handlePoiClick} selectedEntry={selectedPoi} /> <Map
onMouseEnter={handlePoiHoverOn}
onMouseLeave={handlePoiHoverOff}
hoveredPoiId={hoveredPoiId}
values={data as PointOfInterest[]}
onSelect={handlePoiClick}
selectedEntry={selectedPoi}
/>
</div> </div>
); );
} }
......
...@@ -8,6 +8,9 @@ interface Props { ...@@ -8,6 +8,9 @@ interface Props {
values: PointOfInterest[]; values: PointOfInterest[];
onSelect: (id: number) => void; onSelect: (id: number) => void;
selectedEntry?: PointOfInterest | null; selectedEntry?: PointOfInterest | null;
hoveredPoiId?: number | null;
onMouseEnter?: (id: number) => void;
onMouseLeave?: () => void;
} }
const DEFAULT_CENTER: LatLngExpression = [53.550359, 9.986701]; const DEFAULT_CENTER: LatLngExpression = [53.550359, 9.986701];
...@@ -35,7 +38,20 @@ export const Map: React.FC<Props> = (props) => { ...@@ -35,7 +38,20 @@ export const Map: React.FC<Props> = (props) => {
{!!props.selectedEntry && <Marker position={props.selectedEntry.latlng} />} {!!props.selectedEntry && <Marker position={props.selectedEntry.latlng} />}
{!props.selectedEntry && {!props.selectedEntry &&
props.values.map((poi) => ( props.values.map((poi) => (
<Marker key={poi.id} position={poi.latlng} eventHandlers={{ click: () => props.onSelect(poi.id) }} /> <Marker
opacity={props.hoveredPoiId === poi.id ? 1 : 0.7}
key={poi.id}
position={poi.latlng}
eventHandlers={{
click: () => props.onSelect(poi.id),
mouseover: () => {
props.onMouseEnter && props.onMouseEnter(poi.id);
},
mouseout: () => {
props.onMouseLeave && props.onMouseLeave();
},
}}
/>
))} ))}
</MapContainer> </MapContainer>
); );
......
...@@ -12,7 +12,7 @@ const SidebarListElement: React.FC<Props> = ({ value, ...restProps }) => { ...@@ -12,7 +12,7 @@ const SidebarListElement: React.FC<Props> = ({ value, ...restProps }) => {
{...restProps} {...restProps}
className="border-2 border-black border-opacity-20 hover:border-opacity-40 cursor-pointer rounded-lg overflow-hidden mx-4 my-2" className="border-2 border-black border-opacity-20 hover:border-opacity-40 cursor-pointer rounded-lg overflow-hidden mx-4 my-2"
> >
<div className="p-6"> <div className="p-4">
<h2 className="tracking-widest text-xs title-font font-medium text-gray-400 mb-1">{value.category}</h2> <h2 className="tracking-widest text-xs title-font font-medium text-gray-400 mb-1">{value.category}</h2>
<h1 className="title-font text-lg font-medium text-gray-900 mb-3">{value.name}</h1> <h1 className="title-font text-lg font-medium text-gray-900 mb-3">{value.name}</h1>
<p className="leading-relaxed">{value.description}</p> <p className="leading-relaxed">{value.description}</p>
......
...@@ -7,15 +7,23 @@ interface Props { ...@@ -7,15 +7,23 @@ interface Props {
style?: CSSProperties; style?: CSSProperties;
values: PointOfInterest[]; values: PointOfInterest[];
onClick?: (id: number) => void; onClick?: (id: number) => void;
onMouseEnter?: (id: number) => void;
onMouseLeave?: () => void;
} }
const SidebarListView: React.FC<Props> = ({ values, onClick, ...restProps }) => { const SidebarListView: React.FC<Props> = ({ values, onMouseEnter, onMouseLeave, onClick, ...restProps }) => {
return ( return (
values && ( values && (
<SidebarContainer {...restProps}> <SidebarContainer {...restProps}>
<h1 className="text-xl font-medium title-font m-4 text-gray-900 mb-2">{values.length} Orte:</h1> <h1 className="text-xl font-medium title-font m-4 text-gray-900 mb-2">{values.length} Orte:</h1>
{values.map((poi) => ( {values.map((poi) => (
<SidebarListElement key={poi.id} {...(onClick ? { onClick: () => onClick(poi.id) } : {})} value={poi} /> <SidebarListElement
key={poi.id}
{...(onMouseLeave ? { onMouseLeave: () => onMouseLeave() } : {})}
{...(onMouseEnter ? { onMouseEnter: () => onMouseEnter(poi.id) } : {})}
{...(onClick ? { onClick: () => onClick(poi.id) } : {})}
value={poi}
/>
))} ))}
</SidebarContainer> </SidebarContainer>
) )
......
import React, { CSSProperties } from 'react'; import React, { CSSProperties } from 'react';
import SidebarContainer from './SidebarContainer'; import SidebarContainer from './SidebarContainer';
import type { PointOfInterest } from '../types/PointOfInterest'; import type { PointOfInterest } from '../types/PointOfInterest';
import { X as CloseIcon } from 'heroicons-react'; import { X as CloseIcon, HomeOutline as HomeIcon } from 'heroicons-react';
interface Props { interface Props {
style?: CSSProperties; style?: CSSProperties;
...@@ -27,6 +27,14 @@ const SidebarSingleView: React.FC<Props> = ({ value, onClose, ...restProps }) => ...@@ -27,6 +27,14 @@ const SidebarSingleView: React.FC<Props> = ({ value, onClose, ...restProps }) =>
<h2 className="tracking-widest text-xs title-font font-medium text-gray-400 mb-1">{value.category}</h2> <h2 className="tracking-widest text-xs title-font font-medium text-gray-400 mb-1">{value.category}</h2>
<h1 className="title-font text-lg font-medium text-gray-900 mb-3">{value.name}</h1> <h1 className="title-font text-lg font-medium text-gray-900 mb-3">{value.name}</h1>
<p className="leading-relaxed mb-3">{value.description}</p> <p className="leading-relaxed mb-3">{value.description}</p>
{value.website && (
<div className={'flex items-center'}>
<HomeIcon size={20} className={'text-gray-500 mr-2'} />
<a className={'hover:underline'} href={value.website}>
{value.website.replace(/(^\w+:|^)\/\//, '')}
</a>
</div>
)}
</div> </div>
</SidebarContainer> </SidebarContainer>
); );
......
[ [
{ {
"id": 1, "id": 1,
"latlng": [ "latlng": [53.550359, 9.986701],
53.550359,
9.986701
],
"name": "Welcome Werkstatt e. V.", "name": "Welcome Werkstatt e. V.",
"description": "Eine offene Stadtteilwerkstatt in Barmbek-Süd. Hier kann mit Holz, Metall und Elektronik gearbeitet werden.", "description": "Eine offene Stadtteilwerkstatt in Barmbek-Süd. Hier kann mit Holz, Metall und Elektronik gearbeitet werden.",
"address": "Bachstr. 98, 22083 Hamburg", "address": "Bachstr. 98, 22083 Hamburg",
"category": "OFFENE WERKSTATT" "category": "OFFENE WERKSTATT",
"website": "https://www.welcome-werkstatt.de/"
}, },
{ {
"id": 2, "id": 2,
"latlng": [ "latlng": [53.560359, 9.976701],
53.560359,
9.976701
],
"name": "Fabulous St. Pauli", "name": "Fabulous St. Pauli",
"description": "Photo booth fam kinfolk cold-pressed sriracha leggings jianbing microdosing tousled waistcoat.", "description": "Photo booth fam kinfolk cold-pressed sriracha leggings jianbing microdosing tousled waistcoat.",
"address": "Mozartstr. 8, 22081 Hamburg", "address": "Mozartstr. 8, 22081 Hamburg",
"category": "OFFENE WERKSTATT" "category": "OFFENE WERKSTATT",
"website": "http://www.fablab-hamburg.org/"
}, },
{ {
"id": 3, "id": 3,
"latlng": [ "latlng": [53.540359, 9.996701],
53.540359,
9.996701
],
"name": "HoFaLab Wilhelmsburg", "name": "HoFaLab Wilhelmsburg",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text.", "description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text.",
"address": "Langer-Straßen-Name. 128, 22089 Hamburg", "address": "Langer-Straßen-Name. 128, 22089 Hamburg",
"category": "OFFENE WERKSTATT" "category": "OFFENE WERKSTATT",
"website": "https://hofalab.de/de/home-de/"
}, },
{ {
"id": 4, "id": 4,
"latlng": [ "latlng": [53.550359, 9.986701],
53.550359,
9.986701
],
"name": "Fab City Haus", "name": "Fab City Haus",
"description": "Eine offene Stadtteilwerkstatt in Barmbek-Süd. Hier kann mit Holz, Metall und Elektronik gearbeitet werden.", "description": "Eine offene Stadtteilwerkstatt in Barmbek-Süd. Hier kann mit Holz, Metall und Elektronik gearbeitet werden.",
"address": "Jungfernstieg 1, 22083 Hamburg", "address": "Jungfernstieg 1, 22083 Hamburg",
...@@ -45,13 +36,10 @@ ...@@ -45,13 +36,10 @@
}, },
{ {
"id": 5, "id": 5,
"latlng": [ "latlng": [53.530359, 9.966701],
53.530359,
9.966701
],
"name": "Haus Drei e. V.", "name": "Haus Drei e. V.",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text.", "description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text.",
"address": "Hein-Hoyer-Allee 36, 22083 Hamburg", "address": "Hein-Hoyer-Allee 36, 22083 Hamburg",
"category": "OFFENE WERKSTATT" "category": "OFFENE WERKSTATT"
} }
] ]
\ No newline at end of file
import type { LatLngExpression } from "leaflet"; import type { LatLngExpression } from 'leaflet';
export interface PointOfInterest { export interface PointOfInterest {
id: number, id: number;
latlng: LatLngExpression, latlng: LatLngExpression;
name: string, name: string;
description: string, description: string;
address: string, address: string;
category: string category: string;
website?: string;
} }
interface Map { interface Map {
values: PointOfInterest[], values: PointOfInterest[];
onSelect: (entry : PointOfInterest) => void, onSelect: (entry: PointOfInterest) => void;
selectedEntry : PointOfInterest selectedEntry: PointOfInterest;
} }
interface Sidebar { interface Sidebar {
values: PointOfInterest[], values: PointOfInterest[];
onSelect: (entry : PointOfInterest) => void, onSelect: (entry: PointOfInterest) => void;
selectedEntry : PointOfInterest selectedEntry: PointOfInterest;
} }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment