diff --git a/package.json b/package.json index 8e18b2e..5d0f40c 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/public/images/markers/1.png b/public/images/markers/1.png new file mode 100644 index 0000000..a3ed737 Binary files /dev/null and b/public/images/markers/1.png differ diff --git a/public/images/markers/popups/1.svg b/public/images/markers/popups/1.svg new file mode 100644 index 0000000..0aaca09 --- /dev/null +++ b/public/images/markers/popups/1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/header/Navbar.tsx b/src/components/header/Navbar.tsx index 5dd2b73..d86ea48 100644 --- a/src/components/header/Navbar.tsx +++ b/src/components/header/Navbar.tsx @@ -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(null); + const onTabClick = (tab: Tab) => { + setSelectedTab(tab); + }; + return ( ); diff --git a/src/components/header/NavbarTab.tsx b/src/components/header/NavbarTab.tsx new file mode 100644 index 0000000..c03c769 --- /dev/null +++ b/src/components/header/NavbarTab.tsx @@ -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 ( + + ); +}; + +export default NavbarTab; diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx new file mode 100644 index 0000000..825cf17 --- /dev/null +++ b/src/components/map/Map.tsx @@ -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 ( + + + + + + ); +}; + +export default Map; diff --git a/src/components/map/Marker.tsx b/src/components/map/Marker.tsx new file mode 100644 index 0000000..ce4ce6a --- /dev/null +++ b/src/components/map/Marker.tsx @@ -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 ( +
+
+
+
+ {currentMarker && ( + 1 + )} +
+
+
+
+
+
+
+ 1 +
+
+ ); +}; + +export default Marker; diff --git a/src/components/modals/ZoomHint.tsx b/src/components/modals/ZoomHint.tsx index 3281399..0ce7661 100644 --- a/src/components/modals/ZoomHint.tsx +++ b/src/components/modals/ZoomHint.tsx @@ -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 ( -
- - - +
+
+ + + +
+

Zoom and Move to select a location

); }; diff --git a/src/consts/markers.ts b/src/consts/markers.ts new file mode 100644 index 0000000..16399ae --- /dev/null +++ b/src/consts/markers.ts @@ -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 }; diff --git a/src/pages/Main.tsx b/src/pages/Main.tsx index 6d7551f..43e43c1 100644 --- a/src/pages/Main.tsx +++ b/src/pages/Main.tsx @@ -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(); - - return () => {}; }, []); + return (
- - -
- Map -
-
-
+
); }; diff --git a/src/store/useMarker.tsx b/src/store/useMarker.tsx new file mode 100644 index 0000000..5bff8c8 --- /dev/null +++ b/src/store/useMarker.tsx @@ -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((set) => ({ + hoveredMarker: null, + setHoveredMarker: (marker) => set(() => ({ hoveredMarker: marker })), +})); + +export default useMarker; diff --git a/src/types/marker.ts b/src/types/marker.ts new file mode 100644 index 0000000..13598c2 --- /dev/null +++ b/src/types/marker.ts @@ -0,0 +1,7 @@ +import { MarkerComponentProps } from "react-image-marker"; + +type Marker = MarkerComponentProps & { + popup: string; +}; + +export type { Marker }; diff --git a/src/types/tab.ts b/src/types/tab.ts new file mode 100644 index 0000000..a98e2c8 --- /dev/null +++ b/src/types/tab.ts @@ -0,0 +1,7 @@ +interface Tab { + value: string; + id: string; + count: number; +} + +export type { Tab }; diff --git a/yarn.lock b/yarn.lock index 7bdf33b..35d42d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"