feat: implement mobile nav and footer

main
RaviAnand Mohabir 3 months ago
parent 34be1c9ad7
commit e424ae8d0d

@ -18,6 +18,7 @@
}, },
"dependencies": { "dependencies": {
"@ark-ui/react": "^3.9.0", "@ark-ui/react": "^3.9.0",
"@icons-pack/react-simple-icons": "^10.0.0",
"@payloadcms/db-mongodb": "beta", "@payloadcms/db-mongodb": "beta",
"@payloadcms/next": "beta", "@payloadcms/next": "beta",
"@payloadcms/plugin-cloud": "beta", "@payloadcms/plugin-cloud": "beta",

@ -0,0 +1 @@
export default async function About() {}

@ -0,0 +1 @@
export default async function Contact() {}

@ -1,5 +1,6 @@
import "../globals.css"; import "../globals.css";
import Footer from "@/components/layout/footer";
import Navbar from "@/components/layout/navbar"; import Navbar from "@/components/layout/navbar";
import localFont from "next/font/local"; import localFont from "next/font/local";
import { styled } from "@styled-system/jsx"; import { styled } from "@styled-system/jsx";
@ -16,9 +17,12 @@ export const metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
return ( return (
<html lang="en"> <html lang="en">
<styled.body className={moderustic.className} pb={20}> <styled.body className={moderustic.className}>
<Navbar /> <Navbar />
<styled.main mt={20}>{children}</styled.main> <styled.main mt={20} pb={20}>
{children}
</styled.main>
<Footer />
</styled.body> </styled.body>
</html> </html>
); );

@ -0,0 +1 @@
export default async function Menu() {}

@ -26,10 +26,10 @@ export default async function Home() {
<styled.div <styled.div
position="absolute" position="absolute"
color="white" color="white"
top="70%" top="60%"
width="100%" width="100%"
textAlign="center" textAlign="center"
fontSize={24} fontSize={36}
> >
{home.tagline} {home.tagline}
</styled.div> </styled.div>

@ -0,0 +1,42 @@
import { Box, HStack, Stack, styled } from "@styled-system/jsx";
import { IconButton } from "@/components/ui/icon-button";
import { SiFacebook } from "@icons-pack/react-simple-icons";
import { getPayload } from "@/utils/payload";
import { stack } from "@styled-system/patterns";
export default async function Footer() {
const payload = await getPayload();
const about = await payload.findGlobal({ slug: "about" });
const contact = await payload.findGlobal({ slug: "contact" });
return (
<styled.footer h={60} p={8} className={stack({ gap: 4 })}>
<HStack justify="space-between" alignItems="start">
<Box>
<styled.p fontWeight="bold">{about.name}</styled.p>
<styled.p>
{contact.address.street} {contact.address.number}
</styled.p>
<styled.p>
{contact.address.zip} {contact.address.area}
</styled.p>
</Box>
<HStack>
{(contact.socialLinks ?? []).map((sl) => (
<IconButton key={sl.id} asChild variant="link">
<a href={sl.link}>
<SiFacebook />
</a>
</IconButton>
))}
</HStack>
<Stack gap={2}>
{contact.phone && <styled.a href={`tel:${contact.phone}`}>{contact.phone}</styled.a>}
{contact.email && <styled.a href={`mailto:${contact.email}`}>{contact.email}</styled.a>}
</Stack>
</HStack>
<styled.p textAlign="center">Powered by Jenyus</styled.p>
</styled.footer>
);
}

@ -0,0 +1,55 @@
"use client";
import { Menu, X } from "lucide-react";
import { IconButton } from "@/components/ui/icon-button";
import NavLink from "@/components/layout/nav-link";
import { css } from "@styled-system/css";
import { styled } from "@styled-system/jsx";
import { useState } from "react";
export default function MobileNav() {
const [show, setShow] = useState(false);
return (
<styled.div className={css({ display: "block", sm: { display: "none" } })}>
<IconButton onClick={() => setShow((show) => !show)}>
<Menu />
</IconButton>
<styled.div
display={show ? "flex" : "none"}
zIndex={600}
position="fixed"
top={0}
left={0}
right={0}
bottom={0}
height="100%"
width="100%"
flexDir="column"
gap={4}
p={12}
bg="white"
alignItems="center"
onClick={() => setShow(false)}
>
<styled.div alignSelf="stretch" display="flex" justifyContent="end">
<IconButton variant="ghost" onClick={() => setShow(false)}>
<X />
</IconButton>
</styled.div>
<NavLink href="/about">Über uns</NavLink>
<NavLink href="/menu">Menü</NavLink>
<NavLink
href="/contact"
type="button"
className={css({ alignSelf: "stretch", textAlign: "center", marginTop: "auto" })}
>
Kontakt
</NavLink>
</styled.div>
</styled.div>
);
}

@ -0,0 +1,36 @@
import Link, { LinkProps } from "next/link";
import { RecipeVariantProps, cva, cx } from "@styled-system/css";
import { AnchorHTMLAttributes } from "react";
const variants = cva({
base: { _hover: { color: "gray.800" } },
variants: {
type: {
button: {
background: "accent.9",
color: "white",
p: 2,
borderRadius: "md",
_hover: {
background: "accent.11",
},
},
},
},
});
export default function NavLink({
className,
children,
type,
...props
}: RecipeVariantProps<typeof variants> &
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps> &
LinkProps) {
return (
<Link className={cx(variants({ type }), className)} {...props}>
{children}
</Link>
);
}

@ -1,6 +1,8 @@
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { Media } from "@/payload-types"; import { Media } from "@/payload-types";
import MobileNav from "./mobile-nav";
import NavLink from "./nav-link";
import { css } from "@styled-system/css"; import { css } from "@styled-system/css";
import { flex } from "@styled-system/patterns"; import { flex } from "@styled-system/patterns";
import { getPayload } from "@/utils/payload"; import { getPayload } from "@/utils/payload";
@ -41,27 +43,22 @@ export default async function Navbar() {
)} )}
<styled.div flexGrow={1} /> <styled.div flexGrow={1} />
<Link href="/about" className={css({ _hover: { color: "gray.800" } })}> <NavLink href="/about" className={css({ display: "none", sm: { display: "block" } })}>
Über uns Über uns
</Link> </NavLink>
<Link href="/menu" className={css({ _hover: { color: "gray.800" } })}> <NavLink href="/menu" className={css({ display: "none", sm: { display: "block" } })}>
Menü Menü
</Link> </NavLink>
<Link <NavLink
href="/contact" href="/contact"
className={css({ type="button"
background: "accent.9", className={css({ display: "none", sm: { display: "block" } })}
color: "white",
p: 2,
borderRadius: "md",
_hover: {
background: "accent.11",
},
})}
> >
Kontakt Kontakt
</Link> </NavLink>
<MobileNav />
</styled.nav> </styled.nav>
); );
} }

@ -1,4 +1,3 @@
import { DefaultNodeTypes, SerializedBlockNode } from "@payloadcms/richtext-lexical";
import { import {
IS_BOLD, IS_BOLD,
IS_CODE, IS_CODE,
@ -10,10 +9,25 @@ import {
} from "./node-format"; } from "./node-format";
import React, { Fragment, JSX } from "react"; import React, { Fragment, JSX } from "react";
import Link from "next/link"; import { DefaultNodeTypes } from "@payloadcms/richtext-lexical";
import { cva } from "@styled-system/css";
export type NodeTypes = DefaultNodeTypes; export type NodeTypes = DefaultNodeTypes;
const headingRecipe = cva({
base: { fontSize: "xl" },
variants: {
size: {
h1: { fontSize: "xl" },
h2: { fontSize: "2xl" },
h3: { fontSize: "3xl" },
h4: { fontSize: "4xl" },
h5: { fontSize: "5xl" },
h6: { fontSize: "6xl" },
},
},
});
type Props = { type Props = {
nodes: NodeTypes[]; nodes: NodeTypes[];
}; };
@ -88,27 +102,20 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
return <br key={index} />; return <br key={index} />;
} }
case "paragraph": { case "paragraph": {
return ( return <p key={index}>{serializedChildren}</p>;
<p key={index}>
{serializedChildren}
</p>
);
} }
case "heading": { case "heading": {
const Tag = node?.tag; const Tag = node?.tag;
return ( return (
<Tag key={index}> <Tag key={index} className={headingRecipe({ size: Tag })}>
{serializedChildren} {serializedChildren}
</Tag> </Tag>
); );
} }
case "list": { case "list": {
const Tag = node?.tag; const Tag = node?.tag;
return ( return <Tag key={index}>{serializedChildren}</Tag>;
<Tag className="list col-start-2" key={index}>
{serializedChildren}
</Tag>
);
} }
case "listitem": { case "listitem": {
if (node?.checked != null) { if (node?.checked != null) {
@ -117,7 +124,6 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
aria-checked={node.checked ? "true" : "false"} aria-checked={node.checked ? "true" : "false"}
className={` ${node.checked ? "" : ""}`} className={` ${node.checked ? "" : ""}`}
key={index} key={index}
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
role="checkbox" role="checkbox"
tabIndex={-1} tabIndex={-1}
value={node?.value} value={node?.value}
@ -134,11 +140,7 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
} }
} }
case "quote": { case "quote": {
return ( return <blockquote key={index}>{serializedChildren}</blockquote>;
<blockquote className="col-start-2" key={index}>
{serializedChildren}
</blockquote>
);
} }
case "link": { case "link": {
const fields = node.fields; const fields = node.fields;

@ -50,5 +50,43 @@ export const Contact: GlobalConfig = {
}, },
], ],
}, },
{
name: "socialLinks",
type: "array",
fields: [
{
name: "link",
type: "text",
required: true,
},
{
name: "type",
type: "select",
options: [
{
label: "Facebook",
value: "facebook",
},
{
label: "Instagram",
value: "instagram",
},
{
label: "LinkedIn",
value: "linkedin",
},
{
label: "LocalSearch",
value: "localsearch",
},
{
label: "X (Twitter)",
value: "twitter",
},
],
required: true,
},
],
},
], ],
}; };

@ -11,5 +11,11 @@ export const Menu: GlobalConfig = {
type: "relationship", type: "relationship",
relationTo: "media", relationTo: "media",
}, },
{
name: "specials",
type: "relationship",
relationTo: "menu-item",
hasMany: true,
},
], ],
}; };

@ -335,6 +335,13 @@ export interface Contact {
zip: number; zip: number;
area: string; area: string;
}; };
socialLinks?:
| {
link: string;
type: 'facebook' | 'instagram' | 'linkedin' | 'localsearch' | 'twitter';
id?: string | null;
}[]
| null;
updatedAt?: string | null; updatedAt?: string | null;
createdAt?: string | null; createdAt?: string | null;
} }
@ -345,6 +352,7 @@ export interface Contact {
export interface Menu { export interface Menu {
id: string; id: string;
file?: (string | null) | Media; file?: (string | null) | Media;
specials?: (string | MenuItem)[] | null;
updatedAt?: string | null; updatedAt?: string | null;
createdAt?: string | null; createdAt?: string | null;
} }

@ -1421,6 +1421,11 @@
resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz" resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@icons-pack/react-simple-icons@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@icons-pack/react-simple-icons/-/react-simple-icons-10.0.0.tgz#0383d5affe6fadf4ba8a71b9963de1bc394ef37b"
integrity sha512-oU0PVDx9sbNQjRxJN555dsHbRApYN+aBq/O9+wo3JgNkEfvBMgAEtsSGtXWWXQsLAxJcYiFOCzBWege/Xj/JFQ==
"@img/sharp-darwin-arm64@0.33.5": "@img/sharp-darwin-arm64@0.33.5":
version "0.33.5" version "0.33.5"
resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz" resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz"
@ -6916,7 +6921,8 @@ lines-and-columns@^1.1.6:
"@payloadcms/translations" beta "@payloadcms/translations" beta
cross-env "^7.0.3" cross-env "^7.0.3"
graphql "^16.8.1" graphql "^16.8.1"
localbites "file:../../../AppData/Local/Yarn/Cache/v6/npm-localbites-1.0.0-286f440b-5985-41d8-ab09-e59d9c3482f2-1724618578247/node_modules/localbites" localbites "file:../../../AppData/Local/Yarn/Cache/v6/npm-localbites-1.0.0-fb528dd6-b1cf-42f8-b6fa-cc8c7e28e296-1724662317868/node_modules/localbites"
lucide-react "^0.436.0"
next "15.0.0-canary.104" next "15.0.0-canary.104"
payload beta payload beta
react "19.0.0-rc-06d0b89e-20240801" react "19.0.0-rc-06d0b89e-20240801"

Loading…
Cancel
Save