From 90a9c9511e2e9e24933b88da979bd644e4490cac Mon Sep 17 00:00:00 2001 From: Konstantinos Voulgaridis Date: Mon, 30 Dec 2024 17:27:09 +0200 Subject: [PATCH] Feature/convert map layer radio btn popup to a prettier UI (#41) * Replace failed tile with a working and removelegend on comp unmount * Install shadcn and create a button with a popover on the map to replace native tile layers * Replace native leaflet layers control with custom one --- components.json | 21 + package.json | 11 +- pnpm-lock.yaml | 686 ++++++++++++++++++++++- src/components/Map/Legend/Legend.tsx | 4 + src/components/Map/Map.tsx | 38 +- src/components/Map/constants.ts | 35 +- src/components/ui/checkbox.tsx | 26 + src/components/ui/label.tsx | 24 + src/components/ui/popover.tsx | 31 + src/components/ui/radio-group.tsx | 41 ++ src/{constants.ts => constants/index.ts} | 3 +- src/constants/tileLayers.ts | 28 + src/hooks/useStore.tsx | 11 +- src/index.css | 60 +- src/pages/{ => HomePage}/HomePage.tsx | 10 +- src/pages/HomePage/TileLayers.tsx | 64 +++ src/pages/index.tsx | 2 +- src/utils.ts | 13 +- tailwind.config.js | 48 +- tsconfig.json | 6 +- 20 files changed, 1083 insertions(+), 79 deletions(-) create mode 100644 components.json create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/radio-group.tsx rename src/{constants.ts => constants/index.ts} (79%) create mode 100644 src/constants/tileLayers.ts rename src/pages/{ => HomePage}/HomePage.tsx (91%) create mode 100644 src/pages/HomePage/TileLayers.tsx diff --git a/components.json b/components.json new file mode 100644 index 0000000..275d416 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/package.json b/package.json index 324d682..3c40eb1 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,32 @@ { "name": "global-earthquakes", - "version": "4.4.2", + "version": "4.5.2", "private": true, "homepage": "https://kboul.github.io/global-earthquakes", "dependencies": { "@heroicons/react": "^2.2.0", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-radio-group": "^1.2.2", "@tanstack/react-query": "5.62.11", "@types/leaflet": "1.9.15", "@types/node": "22.10.2", "@types/react-leaflet": "3.0.0", "axios": "1.7.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "gh-pages": "6.2.0", "leaflet": "1.9.4", + "lucide-react": "^0.469.0", "react": "18.2.0", "react-dom": "18.2.0", "react-leaflet": "4.2.1", "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", + "tailwind-merge": "^2.6.0", "tailwindcss": "^3.4.17", + "tailwindcss-animate": "^1.0.7", "typescript": "5.7.2", "zustand": "5.0.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95f0c08..785d8d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,18 @@ dependencies: '@heroicons/react': specifier: ^2.2.0 version: 2.2.0(react@18.2.0) + '@radix-ui/react-checkbox': + specifier: ^1.1.3 + version: 1.1.3(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-label': + specifier: ^2.1.1 + version: 2.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-popover': + specifier: ^1.1.4 + version: 1.1.4(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-radio-group': + specifier: ^1.2.2 + version: 1.2.2(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query': specifier: 5.62.11 version: 5.62.11(react@18.2.0) @@ -23,12 +35,21 @@ dependencies: axios: specifier: 1.7.9 version: 1.7.9 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 gh-pages: specifier: 6.2.0 version: 6.2.0 leaflet: specifier: 1.9.4 version: 1.9.4 + lucide-react: + specifier: ^0.469.0 + version: 0.469.0(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -44,9 +65,15 @@ dependencies: react-scripts: specifier: 5.0.1 version: 5.0.1(@babel/plugin-syntax-flow@7.26.0)(@babel/plugin-transform-react-jsx@7.25.9)(eslint@8.57.1)(react@18.2.0)(typescript@5.7.2) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 tailwindcss: specifier: ^3.4.17 version: 3.4.17 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.17) typescript: specifier: 5.7.2 version: 5.7.2 @@ -1743,6 +1770,34 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false + /@floating-ui/core@1.6.8: + resolution: {integrity: sha1-qkNWG+B1gVh5MFllAg9JLNtD2hI=} + dependencies: + '@floating-ui/utils': 0.2.8 + dev: false + + /@floating-ui/dom@1.6.12: + resolution: {integrity: sha1-YzPctajq07K/gvM9a8QQ6V9U5VY=} + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + dev: false + + /@floating-ui/react-dom@2.1.2(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-oTSbv2oOXLXe1V0CN2byCk1DmjE=} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.6.12 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@floating-ui/utils@0.2.8: + resolution: {integrity: sha1-IakHaEcju7ql8JdM93ML15frjmI=} + dev: false + /@heroicons/react@2.2.0(react@18.2.0): resolution: {integrity: sha1-DAUSSvUENKgAdzq+yNOvail9kEs=} peerDependencies: @@ -2144,6 +2199,507 @@ packages: webpack-dev-server: 4.15.2(webpack@5.97.1) dev: false + /@radix-ui/primitive@1.1.1: + resolution: {integrity: sha1-/BaXMtdVx/utM7qNDNf9EMkNyOM=} + dev: false + + /@radix-ui/react-arrow@1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-IQNyGTOov8blO7+9waqtX8i6Ddc=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-checkbox@1.1.3(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-Diq5E/3fPIhgNiX3qUV9c4gsijI=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-collection@1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-vix+AdNQjm1LbYOPSS59GC8X07A=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-b3ZvqpdfhzgmnruKI7rU9ajS+uw=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-context@1.1.1(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-ggdKqDpHI1O7IuhvEby9HGHExxo=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-direction@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-p9OYVfTQd63CoZIvnDU8WXegnNw=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-TuDw+C1Tv1vZ2yFmV5m7DRutXtg=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-hjXt00YwT4tCyuhrBZErYa7yev4=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-XGAhFdHbHE/PoPrkw7CbuJGYU8s=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-id@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-3kczllZZStci64f5Smsl+c/64O0=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-label@2.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-8wvVd7Joc8Y4AG5PZXYdTGuAVm0=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-popover@1.1.4(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-2DEE5ftYiHCmc7VfM4faSETlg24=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.6.2(@types/react@18.3.18)(react@18.2.0) + dev: false + + /@radix-ui/react-popper@1.2.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-L8Zs/DT5XwDYWJJOO+5Uvq4t/wo=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/rect': 1.1.0 + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-portal@1.1.3(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-sOpRQRA6FnG3FUgbE0QHY9KsREA=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-presence@1.1.2(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-u3ZO2KkRi37EUS2l7OMG3thwPNw=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-primitive@2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-bZ78VQ91IBNTZvMz0eggzyJfrZ4=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-radio-group@1.2.2(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-o36b2dgLM7uMG3r4zx3J5QFOUtA=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-roving-focus@1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha1-Ozq7HgNkaTfyjZqyXpY0NmfKZSA=} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.21)(@types/react@18.3.18)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + '@types/react-dom': 18.2.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-slot@1.1.1(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-q5oP+uQCfbfcKvUDwiPJeHBq/8M=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-vOk4ykE2dbyTeUSw0B729KbcW/E=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-EyFEaFe7eGkX31TA1NCEh3qrBLA=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-MaW4fDtyZQS3TgXawe3OdDe5h1Q=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-PCyM4Egnsmo55EL/SIjZISJovSc=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-previous@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-1N03sFUg8dmWo4TrRpMgwq2oN3w=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-rect@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-E7JbkTvT45h8ybBzoaFkuxz0e4g=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/rect': 1.1.0 + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size@1.1.0(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-tNun+9OILuCejSpEo+7Tp+VVJGs=} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.2.0) + '@types/react': 18.3.18 + react: 18.2.0 + dev: false + + /@radix-ui/rect@1.1.0: + resolution: {integrity: sha1-+BfR0yZaxUFdrcZ+2rMK4ZZpZDg=} + dev: false + /@react-leaflet/core@2.1.0(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha1-ODrNMSWdfJro+xsC1eGP5hPCoT0=} peerDependencies: @@ -2590,7 +3146,6 @@ packages: resolution: {integrity: sha1-uMgXFc6967KZQ3hhao1UrOVPBDo=} dependencies: '@types/react': 18.3.18 - dev: true /@types/react-leaflet@3.0.0(leaflet@1.9.4)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha1-snpQq7bjrnNNPBU5mibHfBYcqxw=} @@ -3131,6 +3686,13 @@ packages: resolution: {integrity: sha1-JG9Q88p4oyQPbJl+ipvR6sSeSzg=} dev: false + /aria-hidden@1.2.4: + resolution: {integrity: sha1-t444P9vATQV2LHi0olpQHnNsRSI=} + engines: {node: '>=10'} + dependencies: + tslib: 2.8.1 + dev: false + /aria-query@5.3.2: resolution: {integrity: sha1-k/gaQ0gOM6M48ZFjo9EKUMAdzVk=} engines: {node: '>= 0.4'} @@ -3728,6 +4290,12 @@ packages: resolution: {integrity: sha1-cHQTeE27OnKqEcLysEKgvvQAQXA=} dev: false + /class-variance-authority@0.7.1: + resolution: {integrity: sha1-QAinmKDkVTp4GlesUXfJ+10EN4c=} + dependencies: + clsx: 2.1.1 + dev: false + /clean-css@5.3.3: resolution: {integrity: sha1-szBlPNO9a3UAnMJccUyue5M1HM0=} engines: {node: '>= 10.0'} @@ -3743,6 +4311,11 @@ packages: wrap-ansi: 7.0.0 dev: false + /clsx@2.1.1: + resolution: {integrity: sha1-7tOXyf2L2IK/sY3qtxAgSaLzKZk=} + engines: {node: '>=6'} + dev: false + /co@4.6.0: resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -4331,6 +4904,10 @@ packages: engines: {node: '>=8'} dev: false + /detect-node-es@1.1.0: + resolution: {integrity: sha1-FjrN9kMzDKoLTNfCHn7ndV1vpJM=} + dev: false + /detect-node@2.1.0: resolution: {integrity: sha1-yccHdaScPQO8LAbZpzvlUPl4+LE=} dev: false @@ -5508,6 +6085,11 @@ packages: math-intrinsics: 1.1.0 dev: false + /get-nonce@1.0.1: + resolution: {integrity: sha1-/fPwJ4Bzgg0s6UJsGPB0gbHgzfM=} + engines: {node: '>=6'} + dev: false + /get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha1-tf3nfyLL4185C04ImSLFC85u9mQ=} dev: false @@ -7167,6 +7749,14 @@ packages: yallist: 3.1.1 dev: false + /lucide-react@0.469.0(react@18.2.0): + resolution: {integrity: sha1-8Wk2ymUhSC/vdUp+q7MQ5saOFII=} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + react: 18.2.0 + dev: false + /magic-string@0.25.9: resolution: {integrity: sha1-3n+fr5HvihyR0CwuUxTIJ3283Rw=} dependencies: @@ -8786,6 +9376,41 @@ packages: engines: {node: '>=0.10.0'} dev: false + /react-remove-scroll-bar@2.3.8(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-mcIPkI7kZ7OFtoo0abSj51ABIiM=} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.2.0) + tslib: 2.8.1 + dev: false + + /react-remove-scroll@2.6.2(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-JRjSxREuceqJKPEIKlhFm1x6Kpc=} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.18)(react@18.2.0) + react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.2.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.18)(react@18.2.0) + use-sidecar: 1.1.3(@types/react@18.3.18)(react@18.2.0) + dev: false + /react-router-dom@6.28.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha1-t4/kUtLNMZGbgOVwR6iWv6FQn4w=} engines: {node: '>=14.0.0'} @@ -8907,6 +9532,22 @@ packages: - webpack-plugin-serve dev: false + /react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-QmVgi+aaTXDP4wR/LGyIssOs44g=} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + get-nonce: 1.0.1 + react: 18.2.0 + tslib: 2.8.1 + dev: false + /react@18.2.0: resolution: {integrity: sha1-VVvZhZKIMlX6AN4U8RUakXtdd9U=} engines: {node: '>=0.10.0'} @@ -9877,6 +10518,18 @@ packages: resolution: {integrity: sha1-QwY30ki6d+B4iDlR+5qg7tfGP6I=} dev: false + /tailwind-merge@2.6.0: + resolution: {integrity: sha1-rF+34ieRDAONRY85a3QA2ToxQtU=} + dev: false + + /tailwindcss-animate@1.0.7(tailwindcss@3.4.17): + resolution: {integrity: sha1-MYtpLExCZ2zJ5nsZt4d1dCOIvvQ=} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + dependencies: + tailwindcss: 3.4.17 + dev: false + /tailwindcss@3.4.17: resolution: {integrity: sha1-roQGwPlmlqYxx5B2j/MZ1G1eWmM=} engines: {node: '>=14.0.0'} @@ -10285,6 +10938,37 @@ packages: requires-port: 1.0.0 dev: false + /use-callback-ref@1.3.3(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-mNn6sGcHWEHFssaFIJDV0P6r4r8=} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + react: 18.2.0 + tslib: 2.8.1 + dev: false + + /use-sidecar@1.1.3(@types/react@18.3.18)(react@18.2.0): + resolution: {integrity: sha1-EOf9iX0TC4luLFRsY6XoIz0A79s=} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.18 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.8.1 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} dev: false diff --git a/src/components/Map/Legend/Legend.tsx b/src/components/Map/Legend/Legend.tsx index 46b8833..617406c 100644 --- a/src/components/Map/Legend/Legend.tsx +++ b/src/components/Map/Legend/Legend.tsx @@ -34,6 +34,10 @@ export default function Legend() { }; legend.addTo(map); + + return () => { + map.removeControl(legend); + }; }, [map]); return null; diff --git a/src/components/Map/Map.tsx b/src/components/Map/Map.tsx index 0db3f8d..b91baa5 100644 --- a/src/components/Map/Map.tsx +++ b/src/components/Map/Map.tsx @@ -1,32 +1,28 @@ -import { - MapContainer, - TileLayer, - LayersControl, - GeoJSON, - ScaleControl -} from "react-leaflet"; +import { MapContainer, TileLayer, GeoJSON, ScaleControl } from "react-leaflet"; import Earthquakes from "./Earthquakes"; import Legend from "./Legend"; import tectonicPlates from "./PB2002_boundaries.json"; -import { mapHeight, tectonicPlatesStyle, tileLayers } from "./constants"; +import { useStore } from "../../hooks"; +import { mapHeight, tectonicPlatesStyle } from "./constants"; +import { tileLayers } from "../../constants"; export default function Map() { + const tectonicPlatesOn = useStore((state) => state.tectonicPlatesOn); + const selectedTileLayer = useStore((state) => state.selectedTileLayer); + + const layer = tileLayers.find((layer) => layer.name === selectedTileLayer); + return ( - - {tileLayers.map(({ id, name, attribution, url, checked }) => ( - - - - ))} - - - - + {layer && } + + {tectonicPlatesOn && ( + + )} diff --git a/src/components/Map/constants.ts b/src/components/Map/constants.ts index 195438a..754749d 100644 --- a/src/components/Map/constants.ts +++ b/src/components/Map/constants.ts @@ -1,38 +1,5 @@ const mapHeight = { height: "calc(100vh - 50px)" }; -const tileLayers = [ - { - id: 1, - name: "OpenStreetMap.BlackAndWhite", - attribution: - '© OpenStreetMap contributors', - url: "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png", - checked: false - }, - { - id: 2, - name: "OpenStreetMap.Mapnik", - attribution: - '© OpenStreetMap contributors', - url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - checked: true - }, - { - id: 3, - name: "GoogleStreets", - attribution: "© Google", - url: "http://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}", - checked: false - }, - { - id: 4, - name: "GoogleSatellite", - attribution: "© Google", - url: "http://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}", - checked: false - } -]; - const tectonicPlatesStyle = { color: "orange", weight: 2 @@ -47,4 +14,4 @@ const magnitudeColors = { xxl: "#ff0000" }; -export { magnitudeColors, mapHeight, tileLayers, tectonicPlatesStyle }; +export { magnitudeColors, mapHeight, tectonicPlatesStyle }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..64bddc0 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,26 @@ +import * as React from "react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { Check } from "lucide-react"; + +import { cn } from "../../utils"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..c70955b --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "../../utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..d062d7b --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "../../utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverAnchor = PopoverPrimitive.Anchor; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx new file mode 100644 index 0000000..e9b5156 --- /dev/null +++ b/src/components/ui/radio-group.tsx @@ -0,0 +1,41 @@ +import * as React from "react"; +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; +import { Circle } from "lucide-react"; + +import { cn } from "../../utils"; + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ); +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; + +export { RadioGroup, RadioGroupItem }; diff --git a/src/constants.ts b/src/constants/index.ts similarity index 79% rename from src/constants.ts rename to src/constants/index.ts index 287f821..f895fe7 100644 --- a/src/constants.ts +++ b/src/constants/index.ts @@ -10,4 +10,5 @@ const days = [ { value: "30 days", label: "30 days" } ]; -export { days, initialNumOfDays, initialStartTime }; +export { initialNumOfDays, initialStartTime, days }; +export * from "./tileLayers"; diff --git a/src/constants/tileLayers.ts b/src/constants/tileLayers.ts new file mode 100644 index 0000000..31f2056 --- /dev/null +++ b/src/constants/tileLayers.ts @@ -0,0 +1,28 @@ +export const tileLayers = [ + { + id: 1, + name: "CartoDB.Positron", + attribution: + '© OpenStreetMap contributors © CARTO', + url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png" + }, + { + id: 2, + name: "OpenStreetMap.Mapnik", + attribution: + '© OpenStreetMap contributors', + url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + }, + { + id: 3, + name: "GoogleStreets", + attribution: "© Google", + url: "http://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}" + }, + { + id: 4, + name: "GoogleSatellite", + attribution: "© Google", + url: "http://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}" + } +]; diff --git a/src/hooks/useStore.tsx b/src/hooks/useStore.tsx index ea96a74..fb7456b 100644 --- a/src/hooks/useStore.tsx +++ b/src/hooks/useStore.tsx @@ -1,10 +1,13 @@ import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; +import { CheckedState } from "@radix-ui/react-checkbox"; -import { initialNumOfDays, initialStartTime } from "../constants"; +import { initialNumOfDays, initialStartTime, tileLayers } from "../constants"; interface Store { + selectedTileLayer: string; settingsOpen: boolean; + tectonicPlatesOn: CheckedState; startTime: string; endTime: string; numOfDays: string; @@ -18,8 +21,10 @@ const useStore = create()( devtools( persist( (set) => ({ + selectedTileLayer: tileLayers[1].name, settingsOpen: false, startTime: initialStartTime, + tectonicPlatesOn: false, endTime: "", numOfDays: initialNumOfDays, setStore: (newPair) => set((state) => ({ ...state, ...newPair })), @@ -30,7 +35,9 @@ const useStore = create()( { name: "global-earthquakes-store", partialize: (state) => ({ - settingsOpen: state.settingsOpen + selectedTileLayer: state.selectedTileLayer, + settingsOpen: state.settingsOpen, + tectonicPlatesOn: state.tectonicPlatesOn }) } ) diff --git a/src/index.css b/src/index.css index 2a9909c..e31186f 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,59 @@ -body { - margin: 0; -} - @tailwind base; @tailwind components; @tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 47.4% 11.2%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --card: 0 0% 100%; + --card-foreground: 222.2 47.4% 11.2%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 100% 50%; + --destructive-foreground: 210 40% 98%; + --ring: 215 20.2% 65.1%; + --radius: 0.5rem; + } + + .dark { + --background: 224 71% 4%; + --foreground: 213 31% 91%; + --muted: 223 47% 11%; + --muted-foreground: 215.4 16.3% 56.9%; + --accent: 216 34% 17%; + --accent-foreground: 210 40% 98%; + --popover: 224 71% 4%; + --popover-foreground: 215 20.2% 65.1%; + --border: 216 34% 17%; + --input: 216 34% 17%; + --card: 224 71% 4%; + --card-foreground: 213 31% 91%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 1.2%; + --secondary: 222.2 47.4% 11.2%; + --secondary-foreground: 210 40% 98%; + --destructive: 0 63% 31%; + --destructive-foreground: 210 40% 98%; + --ring: 216 34% 17%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply font-sans antialiased bg-background text-foreground m-0; + } +} diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage/HomePage.tsx similarity index 91% rename from src/pages/HomePage.tsx rename to src/pages/HomePage/HomePage.tsx index 1276b2e..f2742ab 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage/HomePage.tsx @@ -7,10 +7,11 @@ import { AppSelect, DateSelections, Map -} from "../components"; -import { useStore } from "../hooks"; -import { days, initialNumOfDays, initialStartTime } from "../constants"; -import { convertDropdownValue } from "../utils"; +} from "../../components"; +import TileLayers from "./TileLayers"; +import { useStore } from "../../hooks"; +import { convertDropdownValue } from "../../utils"; +import { days, initialNumOfDays, initialStartTime } from "../../constants"; export default function HomePage() { const { @@ -103,6 +104,7 @@ export default function HomePage() { )} + ); } diff --git a/src/pages/HomePage/TileLayers.tsx b/src/pages/HomePage/TileLayers.tsx new file mode 100644 index 0000000..b20da12 --- /dev/null +++ b/src/pages/HomePage/TileLayers.tsx @@ -0,0 +1,64 @@ +import { useState } from "react"; +import { Layers } from "lucide-react"; +import { useShallow } from "zustand/react/shallow"; + +import { + Popover, + PopoverContent, + PopoverTrigger +} from "../../components/ui/popover"; +import { RadioGroup, RadioGroupItem } from "../../components/ui/radio-group"; +import { Checkbox } from "../../components/ui/checkbox"; +import { Label } from "../../components/ui/label"; +import { useStore } from "../../hooks"; +import { tileLayers } from "../../constants"; + +export default function TileLayers() { + const [isOpen, setIsOpen] = useState(false); + + const { selectedTileLayer, tectonicPlatesOn, setStore } = useStore( + useShallow((state) => ({ + selectedTileLayer: state.selectedTileLayer, + tectonicPlatesOn: state.tectonicPlatesOn, + setStore: state.setStore + })) + ); + + return ( + + setIsOpen(true)} + onMouseLeave={() => setIsOpen(false)}> + + + setIsOpen(true)} + onMouseLeave={() => setIsOpen(false)}> + setStore({ selectedTileLayer: value })}> + {tileLayers.map(({ id, name }) => ( +
+ + +
+ ))} +
+ +
+ +
+ + setStore({ tectonicPlatesOn: checked }) + } + /> + +
+
+
+ ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index fc5f898..81b3444 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,3 +1,3 @@ -export { default as HomePage } from "./HomePage"; +export { default as HomePage } from "./HomePage/HomePage"; export { default as ErrorPage } from "./ErrorPage"; export { default as Layout } from "./Layout"; diff --git a/src/utils.ts b/src/utils.ts index 243025b..1cd4723 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,11 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + import { initialNumOfDays } from "./constants"; -const setSearchParam = - (name: string, value: string) => (params: URLSearchParams) => { - params.set(name, value); - return params; - }; +function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} const convertDropdownValue = (dropdownValue: string): string => { const now = "NOW - "; @@ -24,4 +25,4 @@ const convertDropdownValue = (dropdownValue: string): string => { } }; -export { convertDropdownValue, setSearchParam }; +export { convertDropdownValue, cn }; diff --git a/tailwind.config.js b/tailwind.config.js index 7d4011d..63bd12b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,8 +1,50 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./src/**/*.{js,jsx,ts,tsx}' /* src folder, for example */], + darkMode: ["class"], + content: ["src/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"], theme: { - extend: {} + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))" + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))" + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))" + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))" + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))" + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))" + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))" + } + }, + borderRadius: { + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: "calc(var(--radius) - 4px)" + } + } }, - plugins: [] + plugins: [require("tailwindcss-animate")] }; diff --git a/tsconfig.json b/tsconfig.json index cc47c36..547378e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,11 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"] }