Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft: mobile burger menu #754

Closed
wants to merge 11 commits into from
Closed

Conversation

Mara-Li
Copy link
Contributor

@Mara-Li Mara-Li commented Jan 29, 2024

I wanted to add a mobile layout that allow to have the explorer, as a lot of my fellow mate only use phone or use phone more than they computer.

I tweaked a lot and choose to use separate file for drafting, to prevent broke something (and merge error…), and if people want to use the old behavior while testing this.

I think we can (and need) to do better than I have, they are some errors (like the scrolling I can't fully disabled) and some seems to be too much hacky.

The burgerMenu layout needs a specific layout structure (as example) in a docstring in the layout.ts file. Feel free to ask for edit or add PR to my repository directly.

By the way, you can check and test here; https://www.mara-li.fr/ (repository: https://github.com/Lisandra-dev/mara-quartz)

quartz.layout.ts Outdated
@@ -47,3 +47,59 @@ export const defaultListPageLayout: PageLayout = {
],
right: [],
}

/** Example of a valid layout for mobile, allowing burger menu
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to be a lot of duplicate code. Is it possible if we can use conditional displayClass === 'mobile-only'?

only drawback is that explorer would have to be on the left container for this to wor, and set display: grid 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like this

   function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
    constructFileTree(allFiles)
    return (
      <div class={`explorer ${displayClass ?? ""}`}>
        <button
          type="button"
          id="explorer"
          class={displayClass === "mobile-only" ? "collapsed" : ""}
          data-behavior={opts.folderClickBehavior}
          data-collapsed={opts.folderDefaultState}
          data-savestate={opts.useSavedState}
          data-tree={jsonTree}
        >
          {displayClass === "desktop-only" ? (
            <>
              <h1>{opts.title}</h1>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="14"
                height="14"
                viewBox="5 8 14 8"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="fold"
              >
                <polyline points="6 9 12 15 18 9"></polyline>
              </svg>
            </>
          ) : (
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="24"
              height="24"
              viewBox="0 0 24 24"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
              class="lucide-menu"
            >
              <line x1="4" x2="20" y1="12" y2="12" />
              <line x1="4" x2="20" y1="6" y2="6" />
              <line x1="4" x2="20" y1="18" y2="18" />
            </svg>
          )}
        </button>
        <div id="explorer-content">
          <ul class="overflow" id="explorer-ul">
            <ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
            <li id="explorer-end" />
          </ul>
        </div>
      </div>
    )
  }

  Explorer.css = explorerStyle
  Explorer.afterDOMLoaded = script
  return Explorer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it can't works as the button is disabled if the title is empty!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some revision on this

so the explorer function looks like

  function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
    constructFileTree(allFiles)
    return (
      <div class={classNames(displayClass, "explorer")}>
        <button
          type="button"
          id="explorer"
          data-behavior={opts.folderClickBehavior}
          data-collapsed={opts.folderDefaultState}
          data-savestate={opts.useSavedState}
          data-tree={jsonTree}
          data-mobileonly={displayClass === "mobile-only"}
        >
          {displayClass === "desktop-only" ? (
            <>
              <h1>{opts.title}</h1>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="14"
                height="14"
                viewBox="5 8 14 8"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="fold"
              >
                <polyline points="6 9 12 15 18 9"></polyline>
              </svg>
            </>
          ) : (
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="24"
              height="24"
              viewBox="0 0 24 24"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
              class="lucide-menu"
            >
              <line x1="4" x2="20" y1="12" y2="12" />
              <line x1="4" x2="20" y1="6" y2="6" />
              <line x1="4" x2="20" y1="18" y2="18" />
            </svg>
          )}
        </button>
        <div id="explorer-content">
          <ul class="overflow" id="explorer-ul">
            <ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
            <li id="explorer-end" />
          </ul>
        </div>
      </div>
    )
  }

with the inline script where I update setupExplorer and toggleExplorer

...
function toggleExplorer(this: HTMLElement) {
  this.classList.toggle("collapsed")
  const content = this.nextElementSibling as MaybeHTMLElement
  if (!content) return

  content.classList.toggle("collapsed")
  content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
  // prevent scroll under on mobile-only explorer
  if (this.dataset.mobileonly === "true") {
    const center = document.querySelector("#quartz-body .center")
    if (document.querySelector(".mobile-only #explorer")) {
      const queries = [".popover-hint", "footer", "#progress", ".backlinks", ".graph", ".toc"]
      queries.map((query) => {
        document.querySelectorAll(query)?.forEach((element) => {
          element.classList.toggle("no-scroll")
        })
      })
    }
  }
}

function setupExplorer() {
  for (const explorer of document.querySelectorAll("#explorer") as NodeListOf<HTMLElement>) {
    const isMobileOnly = explorer.dataset.mobileonly === "true"
    if (isMobileOnly) {
      explorer.classList.add("collapsed")
      const content = explorer.nextElementSibling as HTMLElement
      content.classList.add("collapsed")
      content.style.maxHeight = "0px"
    }
    if (explorer.dataset.behavior === "collapse") {
      for (const item of document.getElementsByClassName(
        "folder-button",
      ) as HTMLCollectionOf<HTMLElement>) {
        item.removeEventListener("click", toggleFolder)
        item.addEventListener("click", toggleFolder)
      }
    }
   ...
  }
}
...

and then the custom.scss

.left,
.right {
  display: grid !important;
  gap: 1.5rem !important;
  grid-template-columns: 1fr;
  grid-template-rows: repeat(4, min-content);

  .mobile-only.explorer & {
    grid-area: 1 / 1 / 2 / 2;
  }
  .search {
    grid-area: 1 / 1 / 2 / 2;
  }

  .darkmode {
    grid-area: 1 / 2 / 2 / 3;
  }

  .graph {
    grid-area: 1 / 1 / 1 / 3;
  }

  .backlinks {
    grid-area: 3 / 1 / 3 / 3;
  }

  .toc {
    grid-area: 2 / 1 / 3 / 3;
  }

  .recent-notes:nth-last-child(1) {
    grid-area: 2 / 1 / 2 / 3;
  }

  .recent-notes:nth-last-child(2) {
    grid-area: 3 / 1 / 4 / 3;
  }

  @media all and (max-width: $fullPageWidth) {
    display: flex !important;
  }
}

.mobile-only.explorer {
  ul.overflow {
    height: inherit;
    &:after {
      background: none;
    }
  }

  #explorer-content:not(.collapsed) {
    width: 100%;
    min-height: 80vh;
    position: absolute;
    z-index: 100;
    margin-top: 2rem;
    transform: translateX(0);
    overflow: auto;
  }

  #explorer-content.collapsed {
    left: 0;
    width: 0;
    height: inherit;
    position: absolute;
    z-index: 100;
    opacity: 0.35;
    transition:
      0.5s ease opacity,
      0.3s ease color;
  }

  #explorer .lucide-menu {
    stroke: var(--darkgray);
    &:hover {
      stroke: var(--dark);
    }
  }
}

.no-scroll {
  display: none;
  overflow: hidden;
}

Works pretty well. you can tested out on my website.

But then I understand where you come from, this could get hard to maintain pretty quick, since the .no-scroll is pretty hacky ngl.

Copy link
Contributor Author

@Mara-Li Mara-Li Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with display none is to, if you open the burger menu, the page will automatically goes up, and you lost your progression. Also, as I said earlier, I want to disable the collapsing of explorer if no title is set

Copy link
Collaborator

@aarnphm aarnphm Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still save the previous state right? Or am I missing something here?

edit: Ah I see what you mean now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see the difference. On my side, I tweaked my own blog to add a sticky header, and it's not the default behavior. In the default behavior, this question is ignored as you need to scroll to open the menu.

@mayur2281
Copy link

I tried with this, where i moved the Explorer to the right and it worked perfectly fine.

export const defaultContentPageLayout: PageLayout = {
beforeBody: [
Component.Breadcrumbs(),
Component.ArticleTitle(),
Component.ContentMeta(),
Component.TagList(),
],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.Explorer()),
Component.DesktopOnly(Component.RecentNotes({ limit: 5 })),
],
right: [
Component.Graph(),
Component.DesktopOnly(Component.TableOfContents()),
Component.Backlinks(),
Component.MobileOnly(Component.Explorer()),
],
}

@mayur2281
Copy link

My link: notes.mayurbn.site

@ooker777
Copy link

any idea why this PR hasn't accepted?

@Mara-Li
Copy link
Contributor Author

Mara-Li commented Jul 19, 2024

@ooker777

any idea why this PR hasn't accepted?

Because it's more a proof of concept than something usable, who needs a specific setup and a lot of edit to works. Also, I don't think the way i created that is good. Pretty sure that anyone can do a better works tbh (maybe with some library?)

@aarnphm aarnphm mentioned this pull request Aug 17, 2024
11 tasks
@aarnphm
Copy link
Collaborator

aarnphm commented Sep 25, 2024

Superceded by #1441

@aarnphm aarnphm closed this Sep 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants