Skip to content

Commit

Permalink
Merge pull request #15 from Vizzuality/SS-48-contextual-layers
Browse files Browse the repository at this point in the history
Contextual panel
  • Loading branch information
clementprdhomme authored Oct 28, 2024
2 parents 3c9b9ab + d32d4b1 commit d1f5e87
Show file tree
Hide file tree
Showing 55 changed files with 17,097 additions and 719 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# IDE
.idea
2 changes: 2 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cd ./cms && yarn config-sync export -y
cd ../client && yarn types && yarn lint --fix && yarn check-types && git add src/types/generated/
3 changes: 2 additions & 1 deletion client/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.next
/node_modules
/public
/public
/src/types/generated/
1 change: 1 addition & 0 deletions client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"next/core-web-vitals",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@tanstack/query/recommended",
"plugin:import/recommended",
"plugin:import/typescript"
],
Expand Down
3 changes: 3 additions & 0 deletions client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# IDE
.idea
11 changes: 10 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
"version": "0.1.0",
"private": true,
"scripts": {
"prepare": "cd .. && husky",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"types": "orval --config ./src/orval.config.ts",
"check-types": "tsc"
},
"dependencies": {
"@artsy/fresnel": "7.1.4",
Expand All @@ -18,7 +21,9 @@
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-tooltip": "1.1.3",
"@t3-oss/env-nextjs": "0.11.1",
"@tanstack/react-query": "5.59.16",
"@types/mapbox-gl": "3.4.0",
"axios": "1.7.7",
"class-variance-authority": "0.7.0",
"clsx": "2.1.1",
"express": "4.21.1",
Expand All @@ -36,6 +41,7 @@
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/eslint-plugin-query": "5.59.7",
"@types/node": "22.7.6",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
Expand All @@ -47,7 +53,10 @@
"eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "5.2.1",
"husky": "9.1.6",
"jiti": "1.21.6",
"orval": "7.2.0",
"pinst": "3.0.0",
"postcss": "^8",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
Expand Down
5 changes: 4 additions & 1 deletion client/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Jost, DM_Serif_Text } from "next/font/google";
import { NuqsAdapter } from "nuqs/adapters/next/app";

import ReactQueryProvider from "@/app/react-query-provider";
import Head from "@/components/head";

import type { Metadata } from "next";
Expand Down Expand Up @@ -37,7 +38,9 @@ export default function RootLayout({
<html lang="en" className={`${jost.variable} ${dmSerifText.variable}`}>
<Head />
<body>
<NuqsAdapter>{children}</NuqsAdapter>
<ReactQueryProvider>
<NuqsAdapter>{children}</NuqsAdapter>
</ReactQueryProvider>
</body>
</html>
);
Expand Down
43 changes: 43 additions & 0 deletions client/src/app/react-query-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top
import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { PropsWithChildren } from "react";

function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
});
}

let browserQueryClient: QueryClient | undefined = undefined;

function getQueryClient() {
if (isServer) {
// Server: always make a new query client
return makeQueryClient();
} else {
// Browser: make a new query client if we don't already have one
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}

export default function ReactQueryProvider({ children }: PropsWithChildren) {
// NOTE: Avoid useState when initializing the query client if you don't
// have a suspense boundary between this and the code that may
// suspend because React will throw away the client on the initial
// render if it suspends and there is no boundary
const queryClient = getQueryClient();

return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}
26 changes: 26 additions & 0 deletions client/src/components/map/controls/contextual-layers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import ContextualLayersPanel from "@/components/panels/contextual-layers";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import LayersIcon from "@/svgs/layers.svg";

const ContextualLayersControls = () => {
return (
<Sheet>
<SheetTrigger asChild>
<Button type="button" variant="yellow" className="w-8 font-sans xl:w-auto">
<LayersIcon aria-hidden className="xl:!size-5" />
<span className="sr-only xl:not-sr-only">Contextual layers</span>
</Button>
</SheetTrigger>
<SheetContent
side="right"
variant="light"
className="bottom-[68px] h-[calc(100%_-_68px)] xl:bottom-0 xl:h-full"
>
<ContextualLayersPanel />
</SheetContent>
</Sheet>
);
};

export default ContextualLayersControls;
14 changes: 10 additions & 4 deletions client/src/components/map/controls/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import ContextualLayersControls from "./contextual-layers";
import MapSettingsControls from "./map-settings";
import ZoomControls from "./zoom";

const Controls = () => {
return (
<div className="absolute bottom-10 right-5 z-10 flex flex-col gap-2 xl:bottom-6 xl:right-10">
<ZoomControls />
<MapSettingsControls />
</div>
<>
<div className="absolute bottom-20 right-5 z-10 leading-none xl:bottom-auto xl:right-10 xl:top-6">
<ContextualLayersControls />
</div>
<div className="absolute bottom-10 right-5 z-10 flex flex-col gap-2 xl:bottom-6 xl:right-10">
<ZoomControls />
<MapSettingsControls />
</div>
</>
);
};

Expand Down
18 changes: 3 additions & 15 deletions client/src/components/map/controls/zoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,12 @@ const ZoomControls = () => {
const onClickZoomOut = useCallback(() => map?.zoomOut(), [map]);

return (
<div className="flex flex-col gap-px">
<Button
type="button"
variant="yellow"
size="icon"
className="hidden xl:inline-flex"
onClick={onClickZoomIn}
>
<div className="hidden flex-col gap-px xl:flex">
<Button type="button" variant="yellow" size="icon" onClick={onClickZoomIn}>
<span className="sr-only">Zoom in</span>
<PlusIcon aria-hidden />
</Button>
<Button
type="button"
variant="yellow"
size="icon"
className="hidden xl:inline-flex"
onClick={onClickZoomOut}
>
<Button type="button" variant="yellow" size="icon" onClick={onClickZoomOut}>
<span className="sr-only">Zoom out</span>
<MinusIcon aria-hidden />
</Button>
Expand Down
81 changes: 81 additions & 0 deletions client/src/components/panels/contextual-layers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"use client";

import { Button, buttonVariants } from "@/components/ui/button";
import { SheetClose, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton";
import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic";
import XMarkIcon from "@/svgs/xmark.svg";

import Item from "./item";

const ContextualLayersPanel = () => {
const { data, isLoading } = useDatasetsBySubTopic("contextual");

return (
<>
<SheetHeader className="flex items-center justify-between">
<SheetTitle
className={buttonVariants({ variant: "yellow", className: "text-base xl:sr-only" })}
>
Contextual layers
</SheetTitle>
<SheetClose asChild>
<Button
type="button"
variant="yellow"
className="w-8 font-sans text-base transition-transform duration-500 ease-out xl:relative xl:w-auto xl:gap-5 xl:pr-10 xl:group-data-[state=open]:-translate-x-12"
>
<XMarkIcon aria-hidden className="!size-5 xl:!size-6" />
<span className="sr-only">Close</span>
<span aria-hidden className="hidden xl:inline">
Contextual layers
</span>
</Button>
</SheetClose>
</SheetHeader>

<p className="mt-11">
Introduction lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit
amet, consectetu elit.
</p>

{isLoading && (
<>
<Skeleton className="mt-6 h-6 w-1/2" />
<Skeleton className="mt-2 h-6 w-1/3" />
<Skeleton className="mt-2 h-6 w-2/3" />
<Skeleton className="mt-6 h-6 w-1/2" />
<Skeleton className="mt-2 h-6 w-1/3" />
<Skeleton className="mt-2 h-6 w-2/3" />
<Skeleton className="mt-6 h-6 w-1/2" />
<Skeleton className="mt-2 h-6 w-1/3" />
<Skeleton className="mt-2 h-6 w-2/3" />
</>
)}

{!isLoading && data.length === 0 && (
<div className="py-11 text-center font-semibold">Data not available</div>
)}

{!isLoading && data.length > 0 && (
<div className="mt-6 flex flex-col gap-2">
{data.map(({ subTopic, datasets }) => (
<Item
key={subTopic}
name={subTopic}
layers={datasets
.filter((dataset) => dataset.layers.length > 0)
.map((dataset) => ({
// Assuming the dataset has just one layer, which is currently the case
id: dataset.layers[0].id!,
name: dataset.layers[0].attributes!.name!,
}))}
/>
))}
</div>
)}
</>
);
};

export default ContextualLayersPanel;
23 changes: 23 additions & 0 deletions client/src/components/panels/contextual-layers/item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Dataset, Layer } from "@/types/generated/strapi.schemas";

interface ItemProps {
name: Dataset["name"];
layers: { id: number; name: Layer["name"] }[];
}

const Item = ({ name, layers }: ItemProps) => {
return (
<div>
<div className="border-b border-casper-blue-400/50 py-2 uppercase">{name}</div>
<ul className="py-2">
{layers.map((layer) => (
<li key={layer.id} className="py-2">
<span className="text-xl">{layer.name}</span>
</li>
))}
</ul>
</div>
);
};

export default Item;
2 changes: 1 addition & 1 deletion client/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const buttonVariants = cva(
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950",
},
size: {
default: "h-10 px-4 py-2",
default: "h-8 w-auto xl:h-10 px-4 xl:py-2",
icon: "h-8 w-8 xl:h-10 xl:w-10",
auto: "",
},
Expand Down
19 changes: 14 additions & 5 deletions client/src/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,25 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;

const sheetVariants = cva(
"fixed z-10 bg-rhino-blue-900 text-white p-5 transition ease-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-500 data-[state=open]:duration-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950",
"group fixed z-10 p-5 transition ease-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-500 data-[state=open]:duration-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950",
{
variants: {
side: {
top: "inset-x-0 top-0 border-t border-t-white/30 data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-b border-b-white/30 data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-l border-l-white/30 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
left: "inset-y-0 left-0 h-full w-full border-l border-l-white/30 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:w-[400px]",
right:
"inset-y-0 right-0 h-full w-3/4 border-r border-r-white/30 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
"inset-y-0 right-0 h-full w-full border-r border-r-white/30 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:w-[400px]",
},
variant: {
default: "bg-rhino-blue-900 text-white",
light: "bg-white text-rhino-blue-950",
},
},
defaultVariants: {
side: "right",
variant: "default",
},
},
);
Expand All @@ -55,9 +60,13 @@ interface SheetContentProps
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
>(({ side = "right", variant = "default", className, children, ...props }, ref) => (
<SheetPortal>
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side, variant }), className)}
{...props}
>
{children}
</SheetPrimitive.Content>
</SheetPortal>
Expand Down
7 changes: 7 additions & 0 deletions client/src/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { cn } from "@/lib/utils";

function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn("animate-pulse bg-casper-blue-50", className)} {...props} />;
}

export { Skeleton };
3 changes: 3 additions & 0 deletions client/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const env = createEnv({
NEXT_PUBLIC_MAPBOX_TOKEN: z.string(),
/** Mapbox' style URL that contains the basemap */
NEXT_PUBLIC_MAPBOX_STYLE: z.string(),
/** Strapi's instance URL (without trailing slash) */
NEXT_PUBLIC_API_URL: z.string(),
},
/*
* Due to how Next.js bundles environment variables on Edge and Client,
Expand All @@ -35,5 +37,6 @@ export const env = createEnv({
NEXT_PUBLIC_MAPBOX_TOKEN: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
NEXT_PUBLIC_MAPBOX_STYLE: process.env.NEXT_PUBLIC_MAPBOX_STYLE,
NEXT_USE_RESTRICTIVE_ROBOTS_TXT: process.env.NEXT_USE_RESTRICTIVE_ROBOTS_TXT,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});
Loading

0 comments on commit d1f5e87

Please sign in to comment.