Skip to content

Commit

Permalink
feat: add layout, search refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dominik-stumpf committed Jul 6, 2024
1 parent 39e2d9f commit 77b78b7
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 109 deletions.
17 changes: 17 additions & 0 deletions src/app/explorer/atoms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { atom } from "jotai"
import { ActiveSection } from "./types"

export const isNavStuckAtom = atom(false)
export const isSeachStuckAtom = atom(false)
export const activeSectionAtom = atom(ActiveSection.YourGuilds)

// const { ref: navToggleRef, isStuck: isNavStuck } = useIsStuck()
// const { ref: searchRef, isStuck: isSearchStuck } = useIsStuck()
// const [activeSection, setActiveSection] = useState<ActiveSection>(
// ActiveSection.YourGuilds
// )
// const spyActiveSection = useScrollspy(Object.values(ActiveSection), 100)
// useEffect(() => {
// if (!spyActiveSection) return
// setActiveSection(spyActiveSection as ActiveSection)
// }, [spyActiveSection])
183 changes: 93 additions & 90 deletions src/app/explorer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,109 +4,112 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/ToggleGroup"
import { Button } from "@/components/ui/Button"
import { Plus, SignIn } from "@phosphor-icons/react"
import Robot from "/public/landing/robot.svg"
import { Header } from "@/components/Header"
import { Separator } from "@/components/ui/Separator"
import useIsStuck from "hooks/useIsStuck"
import { PageBoundary } from "@/components/PageBoundary"
import { Card } from "@/components/ui/Card"
import { useEffect, useState } from "react"
import useIsStuck from "hooks/useIsStuck"
import { useEffect } from "react"
import useScrollspy from "hooks/useScrollSpy"
import { GuildInfiniteScroll } from "@/components/GuildInfiniteScroll"
import { GuildSearchBar } from "@/components/GuildSeachBar"
import { ActiveSection } from "./types"
import { Anchor } from "@/components/ui/Anchor"
import { Layout } from "@/components/Layout"
import { isNavStuckAtom, isSeachStuckAtom, activeSectionAtom } from "./atoms"
import { useAtom, useAtomValue, useSetAtom } from "jotai"

const Page = () => {
const isAuthenticated = false
const { ref: navToggleRef, isStuck: isNavStuck } = useIsStuck()
const { ref: searchRef, isStuck: isSearchStuck } = useIsStuck()
const [activeSection, setActiveSection] = useState<ActiveSection>(
ActiveSection.YourGuilds
const HeaderBackground = () => {
const isNavStuck = useAtomValue(isNavStuckAtom)
const isSearchStuck = useAtomValue(isSeachStuckAtom)

return (
<div
className="fixed inset-x-0 top-0 z-10 h-40 -translate-y-40 border-b border-border bg-gradient-to-b from-background to-card/30 backdrop-blur backdrop-saturate-150 duration-75 data-[nav-stuck='true']:-translate-y-24 data-[nav-stuck='true']:data-[search-stuck='true']:translate-y-0 motion-safe:transition-transform sm:h-28 sm:-translate-y-28 sm:data-[nav-stuck='true']:-translate-y-12"
data-nav-stuck={isNavStuck}
data-search-stuck={isSearchStuck}
/>
)
}

const Nav = () => {
const isNavStuck = useAtomValue(isNavStuckAtom)
const isSearchStuck = useAtomValue(isSeachStuckAtom)
const [activeSection, setActiveSection] = useAtom(activeSectionAtom)
const spyActiveSection = useScrollspy(Object.values(ActiveSection), 100)
useEffect(() => {
if (!spyActiveSection) return
setActiveSection(spyActiveSection as ActiveSection)
}, [spyActiveSection])
}, [spyActiveSection, setActiveSection])

return (
<div className="flex min-h-screen flex-col">
<div
className="fixed inset-x-0 top-0 z-10 h-40 -translate-y-40 border-b border-border bg-gradient-to-b from-background to-card/30 backdrop-blur backdrop-saturate-150 duration-75 data-[nav-stuck='true']:-translate-y-24 data-[nav-stuck='true']:data-[search-stuck='true']:translate-y-0 motion-safe:transition-transform sm:h-28 sm:-translate-y-28 sm:data-[nav-stuck='true']:-translate-y-12"
data-nav-stuck={isNavStuck}
data-search-stuck={isSearchStuck}
/>
<div className="relative">
<Header />
<PageBoundary>
<h1
className="pb-14 pt-9 font-display text-4xl font-bold tracking-tight text-white sm:text-5xl"
id={ActiveSection.YourGuilds}
>
Guildhall
</h1>
</PageBoundary>
<div className="absolute inset-0 -bottom-28 -z-10 overflow-hidden">
<div className="absolute inset-0 bg-[hsl(240deg_4%_16%)]" />
<div className="absolute inset-0 bg-[url('/banner.png')] bg-[auto_115%] bg-[right_top_10px] bg-no-repeat opacity-10" />
<div className="absolute inset-0 bg-gradient-to-tr from-[hsl(240deg_2.65%_22.16%)] from-50% to-transparent" />
</div>
</div>
<div>
<main>
<PageBoundary>
<div className="sticky top-0 z-10 my-1 py-2" ref={navToggleRef}>
<div className="relative flex items-start justify-between">
<ToggleGroup
type="single"
className="space-x-2"
size={isSearchStuck ? "sm" : "lg"}
variant={isNavStuck ? "default" : "mono"}
onValueChange={(value) =>
value && setActiveSection(value as ActiveSection)
}
value={activeSection}
>
<ToggleGroupItem value={ActiveSection.YourGuilds} asChild>
<a href={`#${ActiveSection.YourGuilds}`}>Your guilds</a>
</ToggleGroupItem>
<ToggleGroupItem value={ActiveSection.ExploreGuilds} asChild>
<a href={`#${ActiveSection.ExploreGuilds}`}>Explore guilds</a>
</ToggleGroupItem>
</ToggleGroup>
{isAuthenticated && (
<Button variant="ghost" className="space-x-2">
<Plus />
<span>Create guild</span>
</Button>
)}
</div>
<ToggleGroup
type="single"
className="space-x-2"
size={isSearchStuck ? "sm" : "lg"}
variant={isNavStuck ? "default" : "mono"}
onValueChange={(value) => value && setActiveSection(value as ActiveSection)}
value={activeSection}
>
<ToggleGroupItem value={ActiveSection.YourGuilds} asChild>
<a href={`#${ActiveSection.YourGuilds}`}>Your guilds</a>
</ToggleGroupItem>
<ToggleGroupItem value={ActiveSection.ExploreGuilds} asChild>
<a href={`#${ActiveSection.ExploreGuilds}`}>Explore guilds</a>
</ToggleGroupItem>
</ToggleGroup>
)
}

const Page = () => {
const isAuthenticated = false
const setIsNavStuck = useSetAtom(isNavStuckAtom)
const setIsSearchStuck = useSetAtom(isSeachStuckAtom)
const { ref: navToggleRef } = useIsStuck(setIsNavStuck)
const { ref: searchRef } = useIsStuck(setIsSearchStuck)

return (
<>
<HeaderBackground />
<Layout.Root>
<Layout.Header>
<div id={ActiveSection.YourGuilds}>
<Layout.Headline title="Guildhall" />
</div>
<Layout.Banner />
</Layout.Header>
<Layout.Main>
<div className="sticky top-0 z-10 my-1 py-2" ref={navToggleRef}>
<div className="relative flex items-start justify-between">
<Nav />
{isAuthenticated && (
<Button variant="ghost" className="space-x-2">
<Plus />
<span>Create guild</span>
</Button>
)}
</div>
</div>
<Card className="my-2 mb-12 flex flex-col items-stretch justify-between gap-8 p-6 font-semibold sm:flex-row sm:items-center">
<div className="flex items-center gap-4">
<Robot className="size-8 min-w-8 text-white" />
<span>Sign in to view your guilds / create new ones</span>
</div>
<Button className="space-x-2">
<SignIn />
<span className="text-md">Sign in</span>
</Button>
</Card>
{isAuthenticated && <Separator className="mb-10" />}
<section id={ActiveSection.ExploreGuilds}>
<h2 className="text-lg font-bold tracking-tight">
Explore verified guilds
</h2>
<div className="sticky top-10 z-10" ref={searchRef}>
<GuildSearchBar />
</div>
<Card className="my-2 mb-12 flex flex-col items-stretch justify-between gap-8 p-6 font-semibold sm:flex-row sm:items-center">
<div className="flex items-center gap-4">
<Robot className="size-8 min-w-8 text-white" />
<span>Sign in to view your guilds / create new ones</span>
</div>
<Button className="space-x-2">
<SignIn />
<span className="text-md">Sign in</span>
</Button>
</Card>
{isAuthenticated && <Separator className="mb-10" />}
<section id={ActiveSection.ExploreGuilds}>
<h2 className="text-lg font-bold tracking-tight">
Explore verified guilds
</h2>
<div className="sticky top-10 z-10" ref={searchRef}>
<GuildSearchBar />
</div>
<GuildInfiniteScroll />
</section>
</PageBoundary>
</main>
</div>
<footer className="mt-auto">
<PageBoundary>
<GuildInfiniteScroll />
</section>
</Layout.Main>
<Layout.Footer>
<p className="my-8 text-center text-sm text-muted-foreground">
This website is{" "}
<Anchor
Expand All @@ -125,9 +128,9 @@ const Page = () => {
Guild SDK
</Anchor>
</p>
</PageBoundary>
</footer>
</div>
</Layout.Footer>
</Layout.Root>
</>
)
}

Expand Down
20 changes: 15 additions & 5 deletions src/hooks/useIsStuck.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { useEffect, useRef, useState } from "react"
import {
Dispatch,
MutableRefObject,
SetStateAction,
useEffect,
useRef,
useState,
} from "react"

/**
* The IntersectionObserver triggers if the element is off the viewport, so we have
* to set top="-1px" or bottom="-1px" on the sticky element instead of 0
*/
const useIsStuck = () => {
const useIsStuck = (
setIsStuck?: Dispatch<SetStateAction<boolean>>
): { ref: MutableRefObject<null>; isStuck?: boolean } => {
const ref = useRef(null)
const [isStuck, setIsStuck] = useState(false)
const [isStuck, setIsStuckLocal] = useState(false)
const setIsStuckActive = setIsStuck ?? setIsStuckLocal

useEffect(() => {
if (!ref.current) return
Expand All @@ -16,7 +26,7 @@ const useIsStuck = () => {

const observer = new IntersectionObserver(
([e]) => {
setIsStuck(
setIsStuckActive(
!e.isIntersecting &&
(e.boundingClientRect.top < topOffsetPx ||
e.boundingClientRect.bottom > bottomOffsetPx)
Expand All @@ -31,7 +41,7 @@ const useIsStuck = () => {
return () => observer.unobserve(cachedRef)
}, [ref])

return { ref, isStuck }
return { ref, isStuck: setIsStuck ? undefined : isStuck }
}

export default useIsStuck
11 changes: 2 additions & 9 deletions src/v2/components/GuildInfiniteScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GuildBase } from "types"
import { fetcherWithSign } from "utils/fetcher"
import { useScrollBatchedRendering } from "hooks/useScrollBatchedRendering"
import { Spinner } from "@phosphor-icons/react"
import { SWRConfiguration } from "swr"

export const guildQueryAtom = atom("")
const BATCH_SIZE = 24
Expand All @@ -26,10 +27,9 @@ const useExploreGuilds = (
guildsInitial: GuildBase[]
) => {
const { isSuperAdmin } = useUser()
const options = {
const options: SWRConfiguration = {
fallbackData: guildsInitial,
dedupingInterval: 60000, // one minute
revalidateFirstPage: false,
}

// sending authed request for superAdmins, so they can see unverified & hideFromExplorer guilds too
Expand Down Expand Up @@ -60,7 +60,6 @@ const useExploreGuilds = (
export const GuildInfiniteScroll = () => {
const searchParams = new URLSearchParams(useAtomValue(guildQueryAtom))
const search = searchParams.get("search")
// const prevSearch = useRef<string | null>();
const ref = useRef<HTMLElement>(null)
const {
data: filteredGuilds,
Expand All @@ -70,12 +69,6 @@ export const GuildInfiniteScroll = () => {
} = useExploreGuilds(searchParams, [])
const renderedGuilds = filteredGuilds?.flat()

// useEffect(() => {
// if (prevSearch.current === search || prevSearch.current === undefined) return
// setSize(1)
// return () => { prevSearch.current = search }
// }, [search, setSize])

useScrollBatchedRendering({
batchSize: 1,
scrollTarget: ref,
Expand Down
10 changes: 5 additions & 5 deletions src/v2/components/GuildSeachBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ export const GuildSearchBar = () => {
([_, value]) => value
)
)
history.replaceState(
null,
"",
`${pathName}${window.location.hash}?${newSearchParams.toString()}`
)
// history.replaceState(
// null,
// "",
// `${pathName}${window.location.hash}?${newSearchParams.toString()}`
// )
setGuildQuery(newSearchParams.toString())
}, [debouncedSearch, order, setGuildQuery, pathName])

Expand Down
7 changes: 7 additions & 0 deletions src/v2/components/Layout/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const Banner = () => (
<div className="absolute inset-0 -bottom-28 -z-10 overflow-hidden">
<div className="absolute inset-0 bg-[hsl(240deg_4%_16%)]" />
<div className="absolute inset-0 bg-[url('/banner.png')] bg-[auto_115%] bg-[right_top_10px] bg-no-repeat opacity-10" />
<div className="absolute inset-0 bg-gradient-to-tr from-[hsl(240deg_2.65%_22.16%)] from-50% to-transparent" />
</div>
)
8 changes: 8 additions & 0 deletions src/v2/components/Layout/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PropsWithChildren } from "react"
import { PageBoundary } from "../PageBoundary"

export const Footer = ({ children }: PropsWithChildren) => (
<footer className="mt-auto">
<PageBoundary>{children}</PageBoundary>
</footer>
)
9 changes: 9 additions & 0 deletions src/v2/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { PropsWithChildren } from "react"
import { Header as NavHeader } from "../Header"

export const Header = ({ children }: PropsWithChildren) => (
<header className="relative">
<NavHeader />
{children}
</header>
)
14 changes: 14 additions & 0 deletions src/v2/components/Layout/Headline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ReactNode } from "react"
import { PageBoundary } from "../PageBoundary"

interface HeadlineProps {
title: ReactNode
}

export const Headline = ({ title }: HeadlineProps) => (
<PageBoundary>
<h1 className="pb-14 pt-9 font-display text-4xl font-bold tracking-tight text-white sm:text-5xl">
{title}
</h1>
</PageBoundary>
)
8 changes: 8 additions & 0 deletions src/v2/components/Layout/Main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PropsWithChildren } from "react"
import { PageBoundary } from "../PageBoundary"

export const Main = ({ children }: PropsWithChildren) => (
<main>
<PageBoundary>{children}</PageBoundary>
</main>
)
5 changes: 5 additions & 0 deletions src/v2/components/Layout/Root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PropsWithChildren } from "react"

export const Root = ({ children }: PropsWithChildren) => (
<div className="flex min-h-screen flex-col">{children}</div>
)
8 changes: 8 additions & 0 deletions src/v2/components/Layout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Banner } from "./Banner"
import { Footer } from "./Footer"
import { Header } from "./Header"
import { Headline } from "./Headline"
import { Main } from "./Main"
import { Root } from "./Root"

export const Layout = { Root, Headline, Banner, Header, Footer, Main }

0 comments on commit 77b78b7

Please sign in to comment.