feat: implement mobile nav and footer

main
RaviAnand Mohabir 3 months ago
parent 34be1c9ad7
commit e424ae8d0d

@ -18,6 +18,7 @@
},
"dependencies": {
"@ark-ui/react": "^3.9.0",
"@icons-pack/react-simple-icons": "^10.0.0",
"@payloadcms/db-mongodb": "beta",
"@payloadcms/next": "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 Footer from "@/components/layout/footer";
import Navbar from "@/components/layout/navbar";
import localFont from "next/font/local";
import { styled } from "@styled-system/jsx";
@ -16,9 +17,12 @@ export const metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<styled.body className={moderustic.className} pb={20}>
<styled.body className={moderustic.className}>
<Navbar />
<styled.main mt={20}>{children}</styled.main>
<styled.main mt={20} pb={20}>
{children}
</styled.main>
<Footer />
</styled.body>
</html>
);

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

@ -26,10 +26,10 @@ export default async function Home() {
<styled.div
position="absolute"
color="white"
top="70%"
top="60%"
width="100%"
textAlign="center"
fontSize={24}
fontSize={36}
>
{home.tagline}
</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 Link from "next/link";
import { Media } from "@/payload-types";
import MobileNav from "./mobile-nav";
import NavLink from "./nav-link";
import { css } from "@styled-system/css";
import { flex } from "@styled-system/patterns";
import { getPayload } from "@/utils/payload";
@ -41,27 +43,22 @@ export default async function Navbar() {
)}
<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
</Link>
<Link href="/menu" className={css({ _hover: { color: "gray.800" } })}>
</NavLink>
<NavLink href="/menu" className={css({ display: "none", sm: { display: "block" } })}>
Menü
</Link>
</NavLink>
<Link
<NavLink
href="/contact"
className={css({
background: "accent.9",
color: "white",
p: 2,
borderRadius: "md",
_hover: {
background: "accent.11",
},
})}
type="button"
className={css({ display: "none", sm: { display: "block" } })}
>
Kontakt
</Link>
</NavLink>
<MobileNav />
</styled.nav>
);
}

@ -1,4 +1,3 @@
import { DefaultNodeTypes, SerializedBlockNode } from "@payloadcms/richtext-lexical";
import {
IS_BOLD,
IS_CODE,
@ -10,10 +9,25 @@ import {
} from "./node-format";
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;
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 = {
nodes: NodeTypes[];
};
@ -88,27 +102,20 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
return <br key={index} />;
}
case "paragraph": {
return (
<p key={index}>
{serializedChildren}
</p>
);
return <p key={index}>{serializedChildren}</p>;
}
case "heading": {
const Tag = node?.tag;
return (
<Tag key={index}>
<Tag key={index} className={headingRecipe({ size: Tag })}>
{serializedChildren}
</Tag>
);
}
case "list": {
const Tag = node?.tag;
return (
<Tag className="list col-start-2" key={index}>
{serializedChildren}
</Tag>
);
return <Tag key={index}>{serializedChildren}</Tag>;
}
case "listitem": {
if (node?.checked != null) {
@ -117,7 +124,6 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
aria-checked={node.checked ? "true" : "false"}
className={` ${node.checked ? "" : ""}`}
key={index}
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
role="checkbox"
tabIndex={-1}
value={node?.value}
@ -134,11 +140,7 @@ export function serializeLexical({ nodes }: Props): JSX.Element {
}
}
case "quote": {
return (
<blockquote className="col-start-2" key={index}>
{serializedChildren}
</blockquote>
);
return <blockquote key={index}>{serializedChildren}</blockquote>;
}
case "link": {
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",
relationTo: "media",
},
{
name: "specials",
type: "relationship",
relationTo: "menu-item",
hasMany: true,
},
],
};

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

@ -1421,6 +1421,11 @@
resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz"
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":
version "0.33.5"
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
cross-env "^7.0.3"
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"
payload beta
react "19.0.0-rc-06d0b89e-20240801"

Loading…
Cancel
Save