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(
+ (
+
+ ),
+ 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
+
+
+
+ )
+}
+
+SearchPlacesBox.displayName = 'SearchPlacesBox'