From 123edb5787bbf41e3fff6770cb01a0beb075b041 Mon Sep 17 00:00:00 2001 From: Alessandro Rabitti Date: Wed, 15 May 2024 10:25:22 +0200 Subject: [PATCH] Added search places functionality --- app/layout.tsx | 3 + app/places/page.tsx | 3 + package-lock.json | 13 ++++ package.json | 2 + src/providers/dialog.tsx | 8 +++ src/styles/globals.css | 10 +-- src/views/dialog/dialog.module.css | 8 +++ src/views/dialog/dialog.tsx | 48 +++++++++++++ src/views/dialog/hooks/useDialog.tsx | 69 +++++++++++++++++++ src/views/dialog/index.ts | 1 + src/views/search-places-box/index.ts | 1 + .../search-places-box.module.css | 20 ++++++ .../search-places-box/search-places-box.tsx | 46 +++++++++++++ 13 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 src/providers/dialog.tsx create mode 100644 src/views/dialog/dialog.module.css create mode 100644 src/views/dialog/dialog.tsx create mode 100644 src/views/dialog/hooks/useDialog.tsx create mode 100644 src/views/dialog/index.ts create mode 100644 src/views/search-places-box/index.ts create mode 100644 src/views/search-places-box/search-places-box.module.css create mode 100644 src/views/search-places-box/search-places-box.tsx diff --git a/app/layout.tsx b/app/layout.tsx index b8c6d83..1a57c4f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from 'react' import { font } from 'src/config/font' import { meta } from 'src/config/meta' +import { DialogProvider } from 'src/providers/dialog' import { Header } from 'src/views/header' @@ -28,6 +29,8 @@ export default function RootLayout({ children }: RootLayoutProps) { + +
diff --git a/app/places/page.tsx b/app/places/page.tsx index bc3c3e2..5c3ac9c 100644 --- a/app/places/page.tsx +++ b/app/places/page.tsx @@ -7,6 +7,7 @@ import { getPlacesList, getTotalPlaces } from 'src/api/place' import { PAGINATION_LIMIT } from 'src/config/pagination' import { ApiQuery } from 'src/types/api' import { Pagination } from 'src/views/pagination' +import { SearchPlacesBox } from 'src/views/search-places-box' export const dynamic = 'force-dynamic' @@ -33,6 +34,8 @@ export default async function PlacesPage(props: PlacePageProps) { return ( <> + + {places.map((place) => (
diff --git a/package-lock.json b/package-lock.json index e0a290a..60008d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "unknown-art", "version": "0.1.0", "dependencies": { + "exenv": "^1.2.2", "fetch-meta-tags": "^1.0.12", "mongodb": "^6.6.1", "next": "^14.2.3", @@ -17,6 +18,7 @@ "devDependencies": { "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", + "@types/exenv": "^1.2.2", "@types/fetch-meta-tags": "^1.0.3", "@types/node": "^20.12.11", "@types/react": "^18.3.2", @@ -1893,6 +1895,12 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-uouAAnjCpcTLuo3Q36hdFa9kg9X4XUL37bQEAfnvmPW9dM2lGcVnafhUIWBWFMUqlxBCpfLcrWuvSAIVSyg1Cg==", + "dev": true + }, "node_modules/@types/fetch-meta-tags": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/fetch-meta-tags/-/fetch-meta-tags-1.0.3.tgz", @@ -4235,6 +4243,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index cc657a3..75b944c 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test": "vitest" }, "dependencies": { + "exenv": "^1.2.2", "fetch-meta-tags": "^1.0.12", "mongodb": "^6.6.1", "next": "^14.2.3", @@ -24,6 +25,7 @@ "devDependencies": { "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", + "@types/exenv": "^1.2.2", "@types/fetch-meta-tags": "^1.0.3", "@types/node": "^20.12.11", "@types/react": "^18.3.2", diff --git a/src/providers/dialog.tsx b/src/providers/dialog.tsx new file mode 100644 index 0000000..22c5dc1 --- /dev/null +++ b/src/providers/dialog.tsx @@ -0,0 +1,8 @@ +import { memo } from 'react' + + +export const DialogProvider = memo(() => { + return
+}) + +DialogProvider.displayName = 'DialogProvider' diff --git a/src/styles/globals.css b/src/styles/globals.css index 2773ba2..4d6b6d9 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -4,15 +4,17 @@ --color-text-primary: white; --color-background-primary: black; --color-background-secondary: #2d2d2d; - --color-border-primary: white; + --color-border-primary: #d3d3d3; + --zindex-dialog: 9; } html, -body { +body, +dialog { background-color: var(--color-background-primary); + border: 0; color: var(--color-text-primary); - font-family: 'Space Mono', 'Helvetica Neue', sans-serif; - margin: 0 var(--spacing-element); + margin: 0; padding: 0; } diff --git a/src/views/dialog/dialog.module.css b/src/views/dialog/dialog.module.css new file mode 100644 index 0000000..af72b85 --- /dev/null +++ b/src/views/dialog/dialog.module.css @@ -0,0 +1,8 @@ +.uadialog { + background-color: black; + height: 100%; + opacity: 0.85; + position: absolute; + width: 100%; + z-index: var(--zindex-dialog); +} diff --git a/src/views/dialog/dialog.tsx b/src/views/dialog/dialog.tsx new file mode 100644 index 0000000..7fd939b --- /dev/null +++ b/src/views/dialog/dialog.tsx @@ -0,0 +1,48 @@ +'use client' + +import { canUseDOM } from 'exenv' +import type { FC, HTMLAttributes, PropsWithChildren } from 'react' +import { createPortal } from 'react-dom' + +import styles from './dialog.module.css' + + +export type DialogProps = + & PropsWithChildren<{ + id: string + toRender?: boolean + open?: boolean + }> + & HTMLAttributes + +export const Dialog: FC = ({ + id, + toRender, + open, + children, + ...props +}) => { + if (!toRender || !canUseDOM) { + return null + } + + const dialogPortal = document.getElementById('dialog-root') + if (!dialogPortal) { + return null + } + + return createPortal( + ( + + {children} + + ), + dialogPortal + ) +} + +Dialog.displayName = 'Dialog' diff --git a/src/views/dialog/hooks/useDialog.tsx b/src/views/dialog/hooks/useDialog.tsx new file mode 100644 index 0000000..6446d57 --- /dev/null +++ b/src/views/dialog/hooks/useDialog.tsx @@ -0,0 +1,69 @@ +'use client' + +import { canUseDOM } from 'exenv' +import { useCallback, useEffect, useMemo, useState, } from 'react' + +import type { DialogProps } from '../dialog' + + +export type UseDialogParams = { + id: string; + open?: boolean; + onOpen?: () => void; + onClose?: () => void; +} + +export type UseDialogHookResult = { + toRender: boolean + dialogProps: DialogProps + openDialog: () => void + closeDialog: () => void +} + +export type UseDialogHook = (options: UseDialogParams) => UseDialogHookResult + +export const useDialog: UseDialogHook = (props) => { + const [toRender, setTodRender] = useState(false) + const [isOpen, setIsOpen] = useState(props?.open ?? false) + + const openDialog = useCallback(() => { + setIsOpen(true) + }, []) + + const closeDialog = useCallback(() => { + setIsOpen(false) + }, []) + + const dialogProps = useMemo(() => { + return { + ...props, + open: isOpen, + 'aria-labelledby': `${props.id}-title`, + 'aria-describedby': `${props.id}-description`, + onOpen(): void { + props.onOpen?.() + }, + onClose(): void { + props.onClose?.() + }, + } + }, [props, isOpen]) + + useEffect(() => { + if (!canUseDOM) { + setTodRender(isOpen) + return + } + + if (isOpen) { + setTodRender(true) + } + }, [isOpen]) + + return { + toRender, + dialogProps, + openDialog, + closeDialog, + } +} diff --git a/src/views/dialog/index.ts b/src/views/dialog/index.ts new file mode 100644 index 0000000..20033cb --- /dev/null +++ b/src/views/dialog/index.ts @@ -0,0 +1 @@ +export * from './dialog' diff --git a/src/views/search-places-box/index.ts b/src/views/search-places-box/index.ts new file mode 100644 index 0000000..95f3862 --- /dev/null +++ b/src/views/search-places-box/index.ts @@ -0,0 +1 @@ +export * from './search-places-box' diff --git a/src/views/search-places-box/search-places-box.module.css b/src/views/search-places-box/search-places-box.module.css new file mode 100644 index 0000000..9c90479 --- /dev/null +++ b/src/views/search-places-box/search-places-box.module.css @@ -0,0 +1,20 @@ +.uasearchplacesbox { + border: 1px solid var(--color-border-primary); + display: flex; + margin-bottom: var(--spacing-element); + margin-left: auto; + padding: var(--spacing-text); + width: max-content; +} + +.uasearchplacesbox__cta { + font-size: smaller; +} + +.uasearchplacesbox__dialog { + align-items: center; + display: flex; + flex-direction: column; + height: 100%; + justify-content: center; +} diff --git a/src/views/search-places-box/search-places-box.tsx b/src/views/search-places-box/search-places-box.tsx new file mode 100644 index 0000000..bcc49bd --- /dev/null +++ b/src/views/search-places-box/search-places-box.tsx @@ -0,0 +1,46 @@ +'use client' + +import type { FC } from 'react' +import { useCallback } from 'react' + +import styles from './search-places-box.module.css' + +import { Dialog } from 'src/views/dialog' +import { useDialog } from 'src/views/dialog/hooks/useDialog' + + + +export const SearchPlacesBox: FC = () => { + const id = 'search-places' + const title = 'Search places' + const description = 'Filter the list of places' + + const { dialogProps, openDialog, closeDialog, toRender } = useDialog({ id }) + + const onSubmit = useCallback(() => { + closeDialog() + }, [closeDialog]) + + return ( +
+ search + + +
+ + +

{title}

+

{description}

+ +
+ + + +
+
+
+
+ ) +} + +SearchPlacesBox.displayName = 'SearchPlacesBox'