Add Sidebar with main components

dev
Platon Yasev 3 years ago
parent efef3fbf8e
commit 81b434b6a2

@ -9,16 +9,22 @@
"preview": "vite preview"
},
"dependencies": {
"antd": "^4.23.6",
"mapbox-gl": "npm:empty-npm-package@1.0.0",
"maplibre-gl": "^2.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-map-gl": "^7.0.19"
"react-icons": "^4.6.0",
"react-map-gl": "^7.0.19",
"tailwind-merge": "^1.7.0"
},
"devDependencies": {
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"@vitejs/plugin-react": "^2.2.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.18",
"tailwindcss": "^3.2.1",
"vite": "^3.2.0"
}
}

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

@ -1,93 +1,8 @@
import "./App.css";
import Map, { Layer, Popup, Source } from "react-map-gl";
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { useRef, useState } from "react";
import {
clusterCountLayer,
clusterLayer,
unclusteredPointLayer,
} from "./clusters";
import { countoursLayer } from "./vector-tile";
import { NavigateButton } from "./NavigateButton";
const MAP_TILER_KEY = "hE7PBueqYiS7hKSYUXP9";
import { MapComponent } from "./Map/MapComponent";
function App() {
const mapRef = useRef(null);
const [showPopup, setShowPopup] = useState(true);
const onClick = (event) => {
try {
console.log(event.features);
const feature = event.features[0];
const clusterId = feature.properties.cluster_id;
const mapboxSource = mapRef.current.getSource("earthquakes");
mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) {
return;
}
mapRef.current.easeTo({
center: feature.geometry.coordinates,
zoom,
duration: 500,
});
});
} catch (err) {
console.error(err);
}
};
return (
<Map
mapLib={maplibregl}
mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
style={{ width: "100vw", height: "100vh" }}
initialViewState={{
latitude: 40.67,
longitude: -103.59,
zoom: 3,
}}
ref={mapRef}
interactiveLayerIds={[clusterLayer.id]}
onClick={onClick}
>
{showPopup && (
<Popup
longitude={-100}
latitude={40}
anchor="bottom"
onClose={() => setShowPopup(false)}
>
<div style={{ color: "red", fontWeight: "bold" }}>You are here</div>
</Popup>
)}
<Source
id="earthquakes"
type="geojson"
data="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
cluster={true}
clusterMaxZoom={14}
clusterRadius={50}
>
<Layer {...clusterLayer} />
<Layer {...clusterCountLayer} />
<Layer {...unclusteredPointLayer} />
</Source>
<Source
id="countours"
type="vector"
url="https://api.maptiler.com/tiles/contours/tiles.json?key=hE7PBueqYiS7hKSYUXP9"
>
<Layer {...countoursLayer} />
</Source>
<NavigateButton />
</Map>
);
return <MapComponent />;
}
export default App;

@ -0,0 +1,91 @@
import maplibregl from "maplibre-gl";
import Map, { Layer, Popup, Source } from "react-map-gl";
import { useRef, useState } from "react";
import {
clusterCountLayer,
clusterLayer,
unclusteredPointLayer,
} from "../clusters";
import { countoursLayer } from "../vector-tile";
import { NavigateButton } from "../NavigateButton";
import { Sidebar } from "../modules/Sidebar/Sidebar";
const MAP_TILER_KEY = "hE7PBueqYiS7hKSYUXP9";
export const MapComponent = () => {
const mapRef = useRef(null);
const [showPopup, setShowPopup] = useState(true);
const onClick = (event) => {
try {
const feature = event.features[0];
if (!feature) return;
const clusterId = feature.properties.cluster_id;
const mapboxSource = mapRef.current.getSource("earthquakes");
mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) {
return;
}
mapRef.current.easeTo({
center: feature.geometry.coordinates,
zoom,
duration: 500,
});
});
} catch (err) {
console.error(err);
}
};
return (
<Map
mapLib={maplibregl}
mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
style={{ width: "100vw", height: "100vh" }}
initialViewState={{
latitude: 55.7558,
longitude: 37.6173,
zoom: 10,
}}
ref={mapRef}
interactiveLayerIds={[clusterLayer.id]}
onClick={onClick}
>
{showPopup && (
<Popup
longitude={-100}
latitude={40}
anchor="bottom"
onClose={() => setShowPopup(false)}
>
<div style={{ color: "red", fontWeight: "bold" }}>You are here</div>
</Popup>
)}
<Source
id="earthquakes"
type="geojson"
data="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
cluster={true}
clusterMaxZoom={14}
clusterRadius={50}
>
<Layer {...clusterLayer} />
<Layer {...clusterCountLayer} />
<Layer {...unclusteredPointLayer} />
</Source>
<Source
id="countours"
type="vector"
url="https://api.maptiler.com/tiles/contours/tiles.json?key=hE7PBueqYiS7hKSYUXP9"
>
<Layer {...countoursLayer} />
</Source>
<NavigateButton />
<Sidebar />
</Map>
);
};

@ -0,0 +1,14 @@
import { Typography } from "antd";
import { twMerge } from "tailwind-merge";
const { Text } = Typography;
export const Title = ({ text, className }) => {
return (
<div className={className}>
<Text type="secondary" className={twMerge("uppercase")}>
{text}
</Text>
</div>
);
};

@ -1,70 +1,3 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -1,10 +1,12 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "maplibre-gl/dist/maplibre-gl.css";
import "antd/dist/antd.css";
import "./index.css";
ReactDOM.createRoot(document.getElementById('root')).render(
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
);

@ -0,0 +1,67 @@
import { Button } from "antd";
import { twMerge } from "tailwind-merge";
import { useState } from "react";
import { Title } from "../../components/Title";
const types = [
{ id: "kiosk", name: "Городские киоски" },
{ id: "mfc", name: "МФЦ" },
{ id: "library", name: "Библиотеки" },
];
const SelectItem = ({ name, isActive, onClick }) => {
return (
<Button
block
type="text"
className={twMerge(
"text-left",
isActive && "bg-blue hover:bg-blue active:bg-blue focus:bg-blue"
)}
onClick={onClick}
>
{name}
</Button>
);
};
export const ObjectTypesSelect = () => {
const [activeTypes, setActiveTypes] = useState([]);
const handleClick = (typeId) =>
setActiveTypes((activeTypes) =>
activeTypes.includes(typeId)
? activeTypes.filter((id) => id !== typeId)
: [...activeTypes, typeId]
);
const clear = () => setActiveTypes([]);
return (
<div>
<div className="flex justify-between items-center mb-1">
<Title text={"Тип объекта размещения"} />
{activeTypes.length !== 0 && (
<Button
type="text"
className="text-grey text-xs p-0 hover:bg-transparent active:bg-transparent focus:bg-transparent h-fit"
onClick={clear}
>
сбросить
</Button>
)}
</div>
<div className="space-y-2">
{types.map((type) => (
<SelectItem
key={type.id}
name={type.name}
isActive={activeTypes.includes(type.id)}
onClick={() => handleClick(type.id)}
/>
))}
</div>
</div>
);
};

@ -0,0 +1,14 @@
import { Slider } from "antd";
import { Title } from "../../components/Title";
export const RatingSlider = () => {
return (
<div>
<Title
text={"Востребованность постамата, усл. ед."}
className="flex justify-center"
/>
<Slider range defaultValue={[20, 50]} />
</div>
);
};

@ -0,0 +1,54 @@
import { TreeSelect } from "antd";
import { useState } from "react";
const { TreeNode } = TreeSelect;
const mockRegions = [
{
id: "ЦАО",
name: "Центральный (ЦАО)",
children: [
{ id: "Арбат", name: "Арбат" },
{ id: "Хамовники", name: "Хамовники" },
],
},
{
id: "ЮАО",
name: "Южный (ЮАО)",
children: [
{ id: "Даниловский", name: "Даниловский" },
{ id: "Нагорный", name: "Нагорный" },
],
},
];
export const RegionSelect = () => {
const [value, setValue] = useState();
const onChange = (newValue) => {
setValue(newValue);
};
return (
<TreeSelect
showSearch
style={{ width: "100%" }}
value={value}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
placeholder="Выберите АО или район"
allowClear
treeDefaultExpandAll
onChange={onChange}
>
{mockRegions.map((parent) => {
return (
<TreeNode key={parent.id} value={parent.id} title={parent.name}>
{parent.children?.map((child) => (
<TreeNode key={child.id} value={child.id} title={child.name} />
))}
</TreeNode>
);
})}
</TreeSelect>
);
};

@ -0,0 +1,29 @@
import { RegionSelect } from "./RegionSelect";
import { Button, Checkbox } from "antd";
import { BsChevronLeft } from "react-icons/bs";
import { ObjectTypesSelect } from "./ObjectTypesSelect";
import { RatingSlider } from "./RatingSlider";
export const Sidebar = () => {
return (
<div className="absolute top-10 right-10 bg-white-background w-[300px] rounded-xl p-3">
<Button
type="text"
icon={<BsChevronLeft className="mr-3" />}
className="flex items-center p-0 pr-1 text-grey mb-2"
>
Настройки
</Button>
<div className="space-y-5">
<RegionSelect />
<ObjectTypesSelect />
<Checkbox>Тепловая карта</Checkbox>
<RatingSlider />
<div className="flex items-center space-x-2">
<Button type="primary">Экспорт данных</Button>
<Button type="primary">Экспорт карты</Button>
</div>
</div>
</div>
);
};

@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,jsx}"],
theme: {
extend: {
colors: {
'white-background': 'rgba(255, 255, 255, 0.8)',
'blue': '#8fc6f9',
'grey': 'rgba(0,0, 0, 0.5)'
},
}
},
plugins: [],
corePlugins: {
preflight: false // <== disable this!
},
}

@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
})
plugins: [react()],
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save