Skip to content

Commit

Permalink
Refactor layout and context management
Browse files Browse the repository at this point in the history
  • Loading branch information
RoelLeijser committed Dec 9, 2024
1 parent b168c37 commit bb64c70
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 81 deletions.
4 changes: 0 additions & 4 deletions browser/create-template/templates/nextjs-site/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import path from 'path';

/** @type {import('next').NextConfig} */
const nextConfig = {
// temporary workaround for using symlinked package with turbopack
outputFileTracingRoot: path.join(import.meta.dirname, '../../../'),
webpack: config => {
config.resolve.extensionAlias = {
'.js': ['.ts', '.tsx', '.js'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { core } from '@tomic/lib';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';

export let currentSubject: string | undefined;

type Params = {
slug?: string[];
};
Expand All @@ -15,7 +13,7 @@ type Props = {
searchParams?: Promise<Record<string, string | string[] | undefined>>;
};

export const fetchResource = async (slug?: string[]) => {
const fetchResource = async (slug?: string[]) => {
const path = slug ? `/${slug.join('/')}` : '/';
return await getCurrentResource(path);
};
Expand All @@ -41,8 +39,6 @@ const Page = async ({ params, searchParams }: Props) => {
return notFound();
}

currentSubject = resource.subject;

return <FullPageView subject={resource.subject} searchParams={search} />;
};

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, {
createContext,
SetStateAction,
Dispatch,
PropsWithChildren,
useState,
useContext,
useEffect,
} from 'react';

const CurrentSubjectContext = createContext<{
currentSubject: string;
setCurrentSubject: Dispatch<SetStateAction<string>>;
}>({
currentSubject: '',
setCurrentSubject: () => '',
});

export const CurrentSubjectProvider = ({
currentSubject: initialSubject,
children,
}: PropsWithChildren<{ currentSubject: string }>) => {
const [currentSubject, setCurrentSubject] = useState(initialSubject);

useEffect(() => {
setCurrentSubject(initialSubject);
}, [initialSubject]);

return (
<CurrentSubjectContext.Provider
value={{ currentSubject, setCurrentSubject }}
>
{children}
</CurrentSubjectContext.Provider>
);
};

export const useCurrentSubject = () => {
const context = useContext(CurrentSubjectContext);

if (!context) {
throw new Error(
'useCurrentSubject must be used within a CurrentSubjectProvider',
);
}

return context;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ export const metadata: Metadata = {
description: 'A Next.js template for Atomic Server',
};

export const dynamic = 'force-dynamic';

export default function RootLayout({
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Container from './Layout/Container';
import HStack from './Layout/HStack';
import { env } from '@/env';
import { Website } from '@/ontologies/website';
import type { Website } from '@/ontologies/website';
import MenuItem from '@/views/MenuItem/MenuItem';
import styles from './Navbar.module.css';
import { store } from '@/store';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
'use client';

import { StoreContext } from '@tomic/react';
import { CurrentSubjectProvider } from '@/app/context/CurrentSubjectContext';
import { CurrentSubjectProvider } from '@/app/context/CurrentSubjectProvider';
import { store } from '@/store';
import { initOntologies } from '@/ontologies';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { usePathname } from 'next/navigation';
import { getCurrentResource } from '@/atomic/getCurrentResource';

const ProviderWrapper = ({ children }: { children: React.ReactNode }) => {
// Registers your ontologies with the store
initOntologies();

const pathname = usePathname();
const [currentSubject, setCurrentSubject] = useState('');

useEffect(() => {
getCurrentResource(pathname).then(resource => {
setCurrentSubject(resource?.subject ?? '');
});
}, [pathname]);

return (
<StoreContext.Provider value={store}>
<CurrentSubjectProvider>{children}</CurrentSubjectProvider>
<CurrentSubjectProvider currentSubject={currentSubject}>
{children}
</CurrentSubjectProvider>
</StoreContext.Provider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ const BlogpostFullPage = ({ resource }: { resource: Resource<Blogpost> }) => {
return (
<Container>
<div className={styles.blogWrapper}>
<Image subject={resource.props.coverImage} alt='' />
<div style={{ height: '25rem' }}>
<Image subject={resource.props.coverImage} alt='' />
</div>
<div className={styles.content}>
<h1 className={styles.h1}>{resource.title}</h1>
<p className={styles.publishDate}>{date}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
}

.button {
anchor-name: var(--anchor-name);
padding: 0.4rem;
display: inline-flex;
align-items: center;
Expand All @@ -14,18 +15,15 @@
background: none;
cursor: pointer;
transition: background-color 100ms ease-in-out;
anchor-name: --menu-item-anchor;
&:hover,
&:focus-visible {
background-color: var(--theme-color-bg-2);
}
}

.submenu {
position-anchor: --menu-item-anchor;
inset-area: bottom center;
position-area: bottom center;
width: max(20ch, anchor-size(width));
--menu-width: 20ch;
width: var(--menu-width);
border: 1px solid var(--theme-color-bg-1);
border-radius: var(--theme-border-radius);
box-shadow:
Expand All @@ -35,4 +33,14 @@
0px 22.3px 17.9px rgba(0, 0, 0, 0.042),
0px 41.8px 33.4px rgba(0, 0, 0, 0.05),
0px 100px 80px rgba(0, 0, 0, 0.07);

position-anchor: var(--anchor-name);
position-area: bottom center;

@supports not (anchor-name: --something) {
--supports-position-anchor: false;
position: fixed;
top: var(--top);
left: var(--left);
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,84 @@
'use client';

import type { MenuItem } from '@/ontologies/website';
import MenuItemLink from './MenuItemLink';
import styles from './MenuItem.module.css';
import { store } from '@/store';
import { useId } from 'react';
import { currentSubject } from '@/app/[[...slug]]/page'; // BAD 👎
import { useResource } from '@tomic/react';
import { useCurrentSubject } from '@/app/context/CurrentSubjectProvider';
import { useId, useRef, useState } from 'react';

const MenuItem = async ({ subject }: { subject: string }) => {
const MenuItem = ({ subject }: { subject: string }) => {
const menuItem = useResource<MenuItem>(subject);
const { currentSubject } = useCurrentSubject();
const id = useId();
const anchorName = `--menuItem-${id}`;
const anchorName = CSS.escape(`--menuItem-${id}`);
const popover = useRef<HTMLDivElement>(null);
const button = useRef<HTMLButtonElement>(null);
const [submenuPosition, setSubmenuPosition] = useState({
top: '0px',
left: '0px',
});

const calcPopoverPosition = () => {
if (!button.current || !popover.current) return;

if (CSS.supports('anchor-name', '--something')) {
return;
}

const rect = button.current.getBoundingClientRect();

const newSubmenuPosition = { ...submenuPosition };

newSubmenuPosition.top = `calc(${rect.top}px + 2rem)`;
newSubmenuPosition.left = `calc(${rect.left}px - (var(--menu-width) / 2 - ${
rect.width / 2
}px))`;

setSubmenuPosition(newSubmenuPosition);
};

const closePopover = () => {
popover.current?.hidePopover();
};

const menuItem = await store.getResource<MenuItem>(subject);
const onFocusOut = (event: React.FocusEvent<HTMLDivElement>) => {
if (
!event.relatedTarget ||
!event.currentTarget.contains(event.relatedTarget)
) {
closePopover();
}
};

return menuItem.props.subItems && menuItem.props.subItems.length > 0 ? (
<>
<button
className={styles.button}
popoverTarget={id}
popoverTargetAction='toggle'
onClick={calcPopoverPosition}
ref={button}
style={{ '--anchor-name': anchorName } as React.CSSProperties}
suppressHydrationWarning
>
{menuItem.title}
</button>

<div
id={id}
className={styles.submenu}
popover='manual'
popover='auto'
ref={popover}
onBlur={onFocusOut}
style={
{
'--top': submenuPosition.top,
'--left': submenuPosition.left,
'--anchor-name': anchorName,
} as React.CSSProperties
}
suppressHydrationWarning
>
<ul className={styles.ul}>
{menuItem.props.subItems?.map((subItem: string, index: number) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,34 @@ import { website } from '@/ontologies/website';
import { unknownSubject, Resource } from '@tomic/lib';
import styles from './MenuItemLink.module.css';
import clsx from 'clsx';
import { store } from '@/store';
import { useResource } from '@tomic/react';
import Link from 'next/link';

const MenuItemLink = async ({
const MenuItemLink = ({
resource,
active = false,
}: {
resource: Resource;
active?: boolean;
}) => {
const page = await store.getResource(resource.subject ?? unknownSubject);
const page = useResource(resource.subject ?? unknownSubject);

const pageHrefValue = await store.getResource(
page.get(website.properties.linksTo),
);
const pageHrefValue = useResource(page.get(website.properties.linksTo));

const href =
pageHrefValue.get(website.properties.href) ??
resource.props.externalLink ??
'#';

return (
<a
<Link
href={href}
className={clsx(styles.link, { [styles.linkActive]: active })}
aria-current={active ? 'page' : 'false'}
suppressHydrationWarning
>
{resource.title}
</a>
{resource.loading ? '' : page.title}
</Link>
);
};

Expand Down

0 comments on commit bb64c70

Please sign in to comment.