Skip to content

Commit

Permalink
Merge pull request #16 from vurak/feature/menu-options-as-links
Browse files Browse the repository at this point in the history
Feature/menu options as links
  • Loading branch information
fbold authored Aug 11, 2024
2 parents 68107e8 + 9192220 commit 6ea6dbe
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 85 deletions.
7 changes: 5 additions & 2 deletions app/(main)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import NavContainer from "@/components/nav/nav-container"

import { version } from "@/package.json"
type Props = {
children?: JSX.Element
}
Expand All @@ -10,8 +10,11 @@ export default async function MainLayout({ children }: Props) {
{/* This contains the orbital menus through another server-side component
which retrieves the user's categories */}
<NavContainer />
<main className="absolute bg-pri dark:bg-pri-d flex-grow h-full w-full overflow-x-clip flex items-center justify-center">
<main className="absolute bg-sec dark:bg-pri-d flex-grow h-full w-full overflow-x-clip flex items-center justify-center">
{children}
<p className="font-mono absolute rotate-90 text-text right-full translate-x-1/2 bottom-40 origin-bottom-left hover:translate-x-full transition-transform pt-6">
({version})
</p>
</main>
</>
)
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/meta/me/categories-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default function CategoriesList({ categories }: CategoriesListProps) {
<div className="flex flex-col items-center text-center w-full">
<InformationPopup
title={`Delete ${selected?.label}?`}
message="All fuTiles of this category will become uncategorized."
message="All documents of this category will become uncategorized."
{...registerPopup}
/>
<p className="underline text-rd">categories</p>
Expand Down
3 changes: 3 additions & 0 deletions app/(main)/meta/visit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Visit() {
return <p>in development</p>
}
13 changes: 8 additions & 5 deletions app/(main)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ export default function Home() {
<span className="text-yw">write</span> for any categories you choose /
you can manage your categories by navigating to{" "}
<span className="text-gn">.me</span> / you can{" "}
<span className="text-rd">read</span> your documents / each document
can be public or private / any document can be sent to the{" "}
<span className="text-white font-semibold">void</span> / you can{" "}
<span className="text-gn">visit</span> other futile users if you know
their username
<span className="text-rd">read</span> your documents / any document
can be sent to the{" "}
<span className="text-white font-semibold">void</span> / documents in
the <span className="text-white font-semibold">void</span> are visible
to any user / there exists no way to link documents in the{" "}
<span className="text-white font-semibold">void</span> to users / you
can <span className="text-gn">visit</span> other users (in
development)
</p>
</div>
</>
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/read/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default async function Read({

return (
<div className="w-full h-full gap-2 overflow-scroll">
<div className="flex flex-col my-12 px-4 sm:mx-0 w-full md:w-2/3 xl:w-1/2 gap-4 relative left-1/2 -translate-x-1/2">
<div className="flex flex-col my-12 px-4 w-full md:w-2/3 lg:w-3/5 gap-4 relative left-1/2 -translate-x-1/2">
<TileList initialTiles={initialTiles} />
</div>
</div>
Expand Down
13 changes: 4 additions & 9 deletions components/nav/nav-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@ export default async function NavContainer() {
const categories = await prisma.category.findMany({
where: { user_id: session.user.id },
})
if (!categories) redirect("/login")

return (
<nav className="fixed flex z-30 w-full h-full pointer-events-none overflow-x-clip">
<div className="top-0 w-full h-[4.2rem] bg-gradient-to-b from-pri via-60% via-pri to-transparent pointer-events-none"></div>
<OrbitalMenus
categories={categories.map((cat) => ({
label: cat.label,
value: cat.id,
}))}
/>
<nav className="fixed z-30 w-full h-full pointer-events-none overflow-x-clip">
<div className="fixed top-0 w-full h-[4.2rem] bg-gradient-smooth-linear-to-b pointer-events-none"></div>
<OrbitalMenus categories={categories} />
<div className="-z-10 fixed bottom-0 w-full h-[4.2rem] bg-gradient-smooth-linear-to-t pointer-events-none"></div>
</nav>
)
}
162 changes: 125 additions & 37 deletions components/nav/orbital-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client"
import { IconChevronUp } from "@tabler/icons-react"
import clsx from "clsx"
import Link from "next/link"
import {
Fragment,
Ref,
Expand Down Expand Up @@ -93,12 +95,17 @@ const torb = {
bl: "bottom",
}

export type OrbitalMenuOption = {
value: string
type MenuOptionCore = {
label: string
id: string
className?: string
isTitle?: boolean
action?: string
href?: string
}

export type OrbitalMenuOption = MenuOptionCore

type OrbitalMenuProps = {
titleOption?: string
options: OrbitalMenuOption[]
Expand Down Expand Up @@ -134,29 +141,35 @@ const OrbitalMenu = (
const options = useMemo(
() =>
titleOption
? [{ label: titleOption, value: "__" }, ...options_]
? [{ label: titleOption, id: "____", isTitle: true }, ...options_]
: options_,
[]
)
const scrollRef = useRef<HTMLDivElement>(null)
const categoriesRefs = useRef(
options.map((c) => createRef<HTMLParagraphElement>())
)
const categoriesRefs = useRef(options.map((c) => createRef<HTMLDivElement>()))
const hideTimeout = useRef<null | NodeJS.Timeout>()
const settleTimeout = useRef<null | NodeJS.Timeout>()
const [activeOption, setActiveOption] = useState(0)
const [angularOffset, setAngularOffset] = useState(0)
const [shown, setShown] = useState(false)
// lkeeps track of which offscreen indicator (arrow) to show
// first one is start (direction of title option)
const [offscreenIndicator, setOffscreenIndicator] = useState([false, false])

useImperativeHandle(
ref,
() => {
return {
home() {
// clear the indicator since can't be anything beyond
setOffscreenIndicator((curr) => [false, curr[1]])
handleCategorySelect(0)
setShown(false)
},
to(idx: number) {
// if not going to title option, add start indicator
if (idx > 0 || (titleOption && idx === 0))
setOffscreenIndicator((curr) => [true, curr[1]])
handleCategorySelect(idx + 1)
},
}
Expand Down Expand Up @@ -201,7 +214,8 @@ const OrbitalMenu = (
// setActiveCategory(tileIdx)
// console.log(titleOption, "Calling categorySelect from handleTileClick()")
handleCategorySelect(optnIdx)
if (options[optnIdx].value !== "__")
// make sure it isnt the title and it is an action option (not href)
if (!options[optnIdx].isTitle && options[optnIdx].action)
// skip setting if home option
onSettle(options[optnIdx])

Expand All @@ -221,7 +235,7 @@ const OrbitalMenu = (
// setAngularOffset(-i * alpha)
// console.log(titleOption, "Calling categorySelect from settleScroll 1")
handleCategorySelect(i)
if (options[i].value !== "__")
if (!options[i].isTitle)
// skip setting if home option
onSettle(options[i])
return
Expand All @@ -238,9 +252,18 @@ const OrbitalMenu = (
[options, onSettle]
)

// useEffect(() => {
// console.log("ON SETTLE CHANGING---------")
// }, [options])
const determineOffscreen = (angularOffset: number) => {
// if angular offset is (approx) 0, nothing is offscreen in start direction
// otherwise it is, as soon as there is angular offset, options start to go offscreen
if (Math.round(angularOffset * 100) == 0)
setOffscreenIndicator((curr) => [false, curr[1]])
else setOffscreenIndicator((curr) => [true, curr[1]])
// If the total angular range of options is bigger than 90 degrees,
// taking into account the angular offset, then options will be offscreen
if ((options.length - 1) * alpha - Math.abs(angularOffset) > 90)
setOffscreenIndicator((curr) => [curr[0], true])
else setOffscreenIndicator((curr) => [curr[0], false])
}

const handleScroll = useCallback(
(event: Event) => {
Expand All @@ -262,6 +285,9 @@ const OrbitalMenu = (
SETTLE_DELAY,
angularOffset_
)

// show or hide offscreen indicators
determineOffscreen(angularOffset_)
},
[alpha, options.length, pos, settleScroll, shown, titleOption]
)
Expand All @@ -278,6 +304,8 @@ const OrbitalMenu = (
})
const ref = scrollRef.current
ref.addEventListener("scroll", handleScroll, { passive: true })
// show or hide offscreen indicators initially
determineOffscreen(angularOffset)

return () => {
ref?.removeEventListener("scroll", handleScroll)
Expand All @@ -300,9 +328,33 @@ const OrbitalMenu = (
return (
<>
<div className="absolute h-full w-full pointer-events-none">
<IconChevronUp
className={clsx(
"z-10 absolute w-4 h-4 transition-opacity duration-200 text-text",
offscreenIndicator[0] && shown ? "opacity-100" : "opacity-0",
`${torb[pos]}-0`,
`${pormx[pos]}translate-x-full ${
torb[pos] === "bottom" ? "rotate-180" : ""
}`
)}
style={
rorl[pos] === "right" ? { right: `${rad}px` } : { left: `${rad}px` }
}
/>
<IconChevronUp
className={clsx(
"z-10 absolute w-4 h-4 transition-opacity duration-200 text-text",
offscreenIndicator[1] && shown ? "opacity-100" : "opacity-0",
`${rorl[pos]}-0`,
`${pormy[pos]}translate-y-full ${pormx[pos]}rotate-90`
)}
style={
torb[pos] === "top" ? { top: `${rad}px` } : { bottom: `${rad}px` }
}
/>
<div
className={`${torb[pos]}-3 ${rorl[pos]}-3 pointer-events-auto
absolute transition-transform aspect-square origin-center`}
absolute transition-transform aspect-square origin-center z-10`}
style={{
transform: `rotateZ(${angularOffset}deg)`,
}}
Expand All @@ -317,13 +369,11 @@ const OrbitalMenu = (
// activeCategory === i ? { ref: activeCategoryRef } : {}
return (
<div
key={option.value}
key={option.id}
className={clsx(
`${torb[pos]}-0 ${rorl[pos]}-0 origin-${origin[pos]}`,
"absolute h-0 cursor-pointer transition-opacity duration-700",
shown || i === activeOption
? "opacity-100"
: "opacity-0 delay-200",
shown || i === activeOption ? "opacity-100" : "opacity-0",
activeOption === i && `text-${colour}`
)}
style={{
Expand All @@ -333,27 +383,51 @@ const OrbitalMenu = (
}}
ref={categoriesRefs.current[i]}
>
<p
className={clsx(
"-translate-y-1/2 whitespace-nowrap",
shown && option.value !== "__"
? "pointer-events-auto"
: "pointer-events-none",
activeOption === i && `text-${colour}`,
option.className || ""
// category.value === "__" && "text-dim"
)}
onClick={(e) => handleTileClick(i)}
ref={categoriesRefs.current[i]}
>
{option.label}
</p>
{option.href ? (
<Link
href={option.href}
className={clsx(
"-translate-y-1/2 whitespace-nowrap block",
shown && !option.isTitle
? "pointer-events-auto"
: "pointer-events-none",
activeOption === i && `text-${colour}`,
option.className || ""
// category.value === "__" && "text-dim"
)}
onClick={(e) => handleTileClick(i)}
ref={categoriesRefs.current[i] as any}
>
{option.label}
</Link>
) : (
<p
className={clsx(
"-translate-y-1/2 whitespace-nowrap",
shown && !option.isTitle
? "pointer-events-auto"
: "pointer-events-none",
activeOption === i && `text-${colour}`,
option.className || ""
// category.value === "__" && "text-dim"
)}
onClick={(e) => handleTileClick(i)}
ref={categoriesRefs.current[i]}
>
{option.label}
</p>
)}
</div>
)
})}
</div>
{pos === "tl" && options[activeOption].value !== "__" ? (
<div className="absolute origin-left flex flex-col">
{pos === "tl" ? (
<div
className={clsx(
"absolute origin-left flex flex-col transition-opacity duration-300",
options[activeOption].isTitle ? "opacity-0" : "opacity-100"
)}
>
<p
className="transition-transform duration-1000 "
style={{
Expand Down Expand Up @@ -387,6 +461,19 @@ const OrbitalMenu = (
${pormx[pos]}translate-x-1/2 ${pormy[pos]}translate-y-1/2
pointer-events-auto`}
>
{/* This is the backdrop to increase visibility of menu options when drawn over text */}
<div
className={clsx(
`absolute ${pormy[pos]}${torb[pos]}-0 ${pormx[pos]}${rorl[pos]}-0 ${pormx[pos]}translate-x-1/4 ${pormy[pos]}translate-y-1/4 pointer-events-none`,
"-z-10 aspect-square rounded-full scale-0 transition-transform duration-300",
"bg-gradient-radial from-sec via-sec via-50% to-transparent",
shown ? "scale-100" : ""
)}
style={{
width: `${5 * rad - 10}px`,
outlineWidth: thickness + "px",
}}
></div>
<div
className={clsx(
`relative ${torb[pos]}-0 ${rorl[pos]}-0`,
Expand All @@ -396,17 +483,18 @@ const OrbitalMenu = (
)}
style={{ width: `${2 * rad - 10}px`, outlineWidth: thickness + "px" }}
>
{/* This is what allows natural scrolling, amount of scroll is proportional to options length */}
<div
className="absolute overflow-auto top-0 bottom-0 left-0 -right-32 h-full scroll-auto"
ref={scrollRef}
onMouseEnter={showCategories}
onMouseLeave={hideCategories}
>
<div className="h-full" />
{options.map((cat) => (
<Fragment key={cat.value}>
<div key={cat + "a"} className="h-full" />
<div key={cat + "b"} className="h-full" />
{options.map((opt) => (
<Fragment key={opt.id}>
<div key={opt + "a"} className="h-full" />
<div key={opt + "b"} className="h-full" />
</Fragment>
))}
</div>
Expand Down
Loading

0 comments on commit 6ea6dbe

Please sign in to comment.