markers, map, button states
This commit is contained in:
@@ -12,8 +12,10 @@
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-image-marker": "^1.2.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-zoom-pan-pinch": "^3.4.4",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.2 MiB |
@@ -1,12 +1,48 @@
|
||||
const tabs = ["Masterplan", "Search", "Favorites", "Company"];
|
||||
import { useState } from "react";
|
||||
import { Tab } from "../../types/tab";
|
||||
import NavbarTab from "./NavbarTab";
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
value: "Masterplan",
|
||||
id: "1",
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
value: "Search",
|
||||
id: "2",
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
value: "Favorites",
|
||||
id: "3",
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
value: "Company",
|
||||
id: "4",
|
||||
count: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const Navbar = () => {
|
||||
const [selectedTab, setSelectedTab] = useState<Tab | null>(null);
|
||||
const onTabClick = (tab: Tab) => {
|
||||
setSelectedTab(tab);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="flex text-[#73787C] self-center col-span-2 justify-center">
|
||||
{tabs.map((tab) => (
|
||||
<button key={tab} className="px-4 py-[10px]">
|
||||
{tab}
|
||||
</button>
|
||||
<NavbarTab
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
isSelected={selectedTab?.id === tab.id}
|
||||
onClick={onTabClick}
|
||||
/>
|
||||
// <button key={tab} className="px-4 py-[10px]">
|
||||
// {tab}
|
||||
// </button>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Tab } from "../../types/tab";
|
||||
|
||||
interface NavbarTabProps {
|
||||
tab: Tab;
|
||||
isSelected: boolean;
|
||||
onClick: (tab: Tab) => void;
|
||||
}
|
||||
|
||||
const NavbarTab = ({ tab, onClick, isSelected = false }: NavbarTabProps) => {
|
||||
return (
|
||||
<button
|
||||
className="px-4 text-[#73787C] hover:text-black relative"
|
||||
onClick={() => onClick(tab)}
|
||||
>
|
||||
<div
|
||||
className={`py-[10px] border-b transition-all duration-300 ${
|
||||
isSelected
|
||||
? "border-b-[#00BED7]"
|
||||
: "border-b-transparent hover:border-b-[#E2E2DC] active:border-b-[#00BED7]"
|
||||
}`}
|
||||
>
|
||||
{tab.value}
|
||||
</div>
|
||||
{tab.count !== 0 && (
|
||||
<div className="absolute top-0 right-0 w-4 h-4 bg-[#00BED7] rounded-full text-white text-[10px] flex items-center justify-center">
|
||||
{tab.count}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavbarTab;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
import ImageMarker from "react-image-marker";
|
||||
import { MarkerComponentProps } from "react-image-marker";
|
||||
import Marker from "./Marker";
|
||||
import { markers } from "../../consts/markers";
|
||||
|
||||
const Map = () => {
|
||||
const imageMarkers: MarkerComponentProps[] = markers.map((marker) => {
|
||||
return {
|
||||
top: marker.top,
|
||||
left: marker.left,
|
||||
itemNumber: marker.itemNumber,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<TransformWrapper
|
||||
alignmentAnimation={{ sizeX: 0, sizeY: 0 }}
|
||||
wheel={{ step: 1 }}
|
||||
zoomAnimation={{
|
||||
disabled: false,
|
||||
size: 0,
|
||||
animationType: "easeOutQuart",
|
||||
animationTime: 2000,
|
||||
}}
|
||||
// velocityAnimation={{
|
||||
// sensitivity: 1000,
|
||||
// animationTime: 1000,
|
||||
// animationType: "easeOut",
|
||||
// }}
|
||||
>
|
||||
<TransformComponent wrapperClass="h-[calc(100vh-60px)]">
|
||||
<ImageMarker
|
||||
src="images/Map.jpg"
|
||||
markers={imageMarkers}
|
||||
markerComponent={Marker}
|
||||
/>
|
||||
</TransformComponent>
|
||||
</TransformWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { MarkerComponentProps } from "react-image-marker";
|
||||
import { markers } from "../../consts/markers";
|
||||
import useMarker from "../../store/useMarker";
|
||||
|
||||
const Marker = (props: MarkerComponentProps) => {
|
||||
const { hoveredMarker, setHoveredMarker } = useMarker();
|
||||
const currentMarker = markers.find(
|
||||
(marker) => marker.itemNumber === props.itemNumber
|
||||
);
|
||||
|
||||
const handleOnMouseEnter = () => {
|
||||
const hoveredMarker = currentMarker ? currentMarker : null;
|
||||
setHoveredMarker(hoveredMarker);
|
||||
};
|
||||
|
||||
const handleOnMouseLeave = () => {
|
||||
setHoveredMarker(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
<div
|
||||
className={`flex items-end transition-opacity duration-300 ${
|
||||
hoveredMarker?.itemNumber === currentMarker?.itemNumber
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center py-2">
|
||||
<div className="bg-white px-2 py-1 rounded-lg">
|
||||
{currentMarker && (
|
||||
<img src={currentMarker.popup} alt="1" width={48} height={24} />
|
||||
)}
|
||||
</div>
|
||||
<div className="relative w-[5.5px] h-2">
|
||||
<div className="w-0 h-0 border-y-4 border-l-8 border-r-0 border-transparent border-l-[#fff] absolute top-0 -left-[2.5px]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div onMouseEnter={handleOnMouseEnter} onMouseLeave={handleOnMouseLeave}>
|
||||
<img src="/images/markers/1.png" alt="1" width={48} height={65} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Marker;
|
||||
@@ -1,13 +1,57 @@
|
||||
import { useOnClickOutside } from "usehooks-ts";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import SearchPlusIcon from "../icons/SearchIcon";
|
||||
import OpenFullscreenIcon from "../icons/OpenFullscreenIcon";
|
||||
import LineIcon from "../icons/LineIcon";
|
||||
import useModal from "../../store/useModal";
|
||||
|
||||
const ZoomHint = () => {
|
||||
const { setModal } = useModal();
|
||||
const [isTransparent, setIsTransparent] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
const handleOnScroll = () => {
|
||||
setIsTransparent(true);
|
||||
|
||||
const timeOut = setTimeout(() => {
|
||||
setModal(null);
|
||||
clearTimeout(timeOut);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleClickOutside = () => {
|
||||
setIsTransparent(true);
|
||||
|
||||
const timeOut = setTimeout(() => {
|
||||
setModal(null);
|
||||
clearTimeout(timeOut);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const map = document.querySelector(".react-transform-wrapper");
|
||||
if (!map) return;
|
||||
map.addEventListener("wheel", handleOnScroll);
|
||||
return () => {
|
||||
map.removeEventListener("wheel", handleOnScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useOnClickOutside(ref, handleClickOutside);
|
||||
|
||||
return (
|
||||
<div className="absolute z-10 m-auto top-1/2 left-1/2 flex bg-[#0D192266] p-4 text-white gap-4 items-center">
|
||||
<SearchPlusIcon />
|
||||
<LineIcon />
|
||||
<OpenFullscreenIcon />
|
||||
<div
|
||||
className={`absolute z-10 m-auto top-1/2 left-1/2 flex flex-col items-center bg-[#0D192266] p-4 text-white gap-3 rounded-lg transition-opacity duration-300 ${
|
||||
isTransparent ? "opacity-0" : "opacity-100"
|
||||
}`}
|
||||
ref={ref}
|
||||
>
|
||||
<div className="flex gap-4 items-center">
|
||||
<SearchPlusIcon />
|
||||
<LineIcon />
|
||||
<OpenFullscreenIcon />
|
||||
</div>
|
||||
<p>Zoom and Move to select a location</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Marker } from "../types/marker";
|
||||
|
||||
const markers: Marker[] = [
|
||||
{
|
||||
top: 38.5,
|
||||
left: 51,
|
||||
itemNumber: 0,
|
||||
popup: "/images/markers/popups/1.svg",
|
||||
},
|
||||
{
|
||||
top: 40.5,
|
||||
left: 52,
|
||||
itemNumber: 1,
|
||||
popup: "/images/markers/popups/1.svg",
|
||||
},
|
||||
];
|
||||
|
||||
export { markers };
|
||||
+4
-14
@@ -1,27 +1,17 @@
|
||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
import useModal from "../store/useModal";
|
||||
import { useEffect } from "react";
|
||||
import useModal from "../store/useModal";
|
||||
import ZoomHint from "../components/modals/ZoomHint";
|
||||
import Map from "../components/map/Map";
|
||||
|
||||
const Main = () => {
|
||||
const { setModal } = useModal();
|
||||
useEffect(() => {
|
||||
setModal(<ZoomHint />);
|
||||
|
||||
return () => {};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TransformWrapper
|
||||
alignmentAnimation={{ sizeX: 0, sizeY: 0 }}
|
||||
zoomAnimation={{ size: 0 }}
|
||||
>
|
||||
<TransformComponent wrapperClass="h-[calc(100vh-60px)]">
|
||||
<div>
|
||||
<img src="images/Map.jpg" alt="Map" />
|
||||
</div>
|
||||
</TransformComponent>
|
||||
</TransformWrapper>
|
||||
<Map />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { create } from "zustand";
|
||||
import { Marker } from "../types/marker";
|
||||
|
||||
interface ModalStore {
|
||||
hoveredMarker: Marker | null;
|
||||
setHoveredMarker: (marker: Marker | null) => void;
|
||||
}
|
||||
|
||||
const useMarker = create<ModalStore>((set) => ({
|
||||
hoveredMarker: null,
|
||||
setHoveredMarker: (marker) => set(() => ({ hoveredMarker: marker })),
|
||||
}));
|
||||
|
||||
export default useMarker;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { MarkerComponentProps } from "react-image-marker";
|
||||
|
||||
type Marker = MarkerComponentProps & {
|
||||
popup: string;
|
||||
};
|
||||
|
||||
export type { Marker };
|
||||
@@ -0,0 +1,7 @@
|
||||
interface Tab {
|
||||
value: string;
|
||||
id: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export type { Tab };
|
||||
@@ -1496,6 +1496,11 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||
@@ -1777,6 +1782,11 @@ react-dom@^18.2.0:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-image-marker@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-image-marker/-/react-image-marker-1.2.0.tgz#2f964a826aaa8c6b18c4ce7cedb0284f8eab8b6c"
|
||||
integrity sha512-HFrzKVnX/hgZqHlxwV7XQNiyMRowD1IAnbsf4dZCEuSzGlGHxAv+sCv/AU1VHHVxFxoHubNL/xYNpGQfgtX67A==
|
||||
|
||||
react-refresh@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz"
|
||||
@@ -1928,6 +1938,7 @@ source-map-js@^1.2.0:
|
||||
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
||||
name string-width-cjs
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -2102,6 +2113,13 @@ use-sync-external-store@1.2.0:
|
||||
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
usehooks-ts@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-3.1.0.tgz#156119f36efc85f1b1952616c02580f140950eca"
|
||||
integrity sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==
|
||||
dependencies:
|
||||
lodash.debounce "^4.0.8"
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
|
||||
Reference in New Issue
Block a user