mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-05-18 14:34:23 +02:00
Rewrite mobile explorer
This commit is contained in:
parent
b0c079f24a
commit
a190cba737
@ -27,7 +27,7 @@ export const defaultContentPageLayout: PageLayout = {
|
|||||||
Component.MobileOnly(Component.Spacer()),
|
Component.MobileOnly(Component.Spacer()),
|
||||||
Component.Search(),
|
Component.Search(),
|
||||||
Component.Darkmode(),
|
Component.Darkmode(),
|
||||||
Component.DesktopOnly(Component.Explorer()),
|
Component.Explorer(),
|
||||||
],
|
],
|
||||||
right: [
|
right: [
|
||||||
Component.Graph(),
|
Component.Graph(),
|
||||||
|
@ -5,7 +5,6 @@ import explorerStyle from "./styles/explorer.scss"
|
|||||||
import script from "./scripts/explorer.inline"
|
import script from "./scripts/explorer.inline"
|
||||||
import { ExplorerNode, FileNode, Options } from "./ExplorerNode"
|
import { ExplorerNode, FileNode, Options } from "./ExplorerNode"
|
||||||
import { QuartzPluginData } from "../plugins/vfile"
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
import { classNames } from "../util/lang"
|
|
||||||
import { i18n } from "../i18n"
|
import { i18n } from "../i18n"
|
||||||
|
|
||||||
// Options interface defined in `ExplorerNode` to avoid circular dependency
|
// Options interface defined in `ExplorerNode` to avoid circular dependency
|
||||||
@ -13,6 +12,7 @@ const defaultOptions = {
|
|||||||
folderClickBehavior: "collapse",
|
folderClickBehavior: "collapse",
|
||||||
folderDefaultState: "collapsed",
|
folderDefaultState: "collapsed",
|
||||||
useSavedState: true,
|
useSavedState: true,
|
||||||
|
usePagePath: false,
|
||||||
mapFn: (node) => {
|
mapFn: (node) => {
|
||||||
return node
|
return node
|
||||||
},
|
},
|
||||||
@ -46,7 +46,7 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
let jsonTree: string
|
let jsonTree: string
|
||||||
let lastBuildId: string = ""
|
let lastBuildId: string = ""
|
||||||
|
|
||||||
function constructFileTree(allFiles: QuartzPluginData[]) {
|
function constructFileTree(allFiles: QuartzPluginData[], currentFilePath: string) {
|
||||||
// Construct tree from allFiles
|
// Construct tree from allFiles
|
||||||
fileTree = new FileNode("")
|
fileTree = new FileNode("")
|
||||||
allFiles.forEach((file) => fileTree.add(file))
|
allFiles.forEach((file) => fileTree.add(file))
|
||||||
@ -68,7 +68,10 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
|
|
||||||
// Get all folders of tree. Initialize with collapsed state
|
// Get all folders of tree. Initialize with collapsed state
|
||||||
// Stringify to pass json tree as data attribute ([data-tree])
|
// Stringify to pass json tree as data attribute ([data-tree])
|
||||||
const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
|
const folders = fileTree.getFolderPaths(
|
||||||
|
opts.folderDefaultState === "collapsed",
|
||||||
|
currentFilePath,
|
||||||
|
)
|
||||||
jsonTree = JSON.stringify(folders)
|
jsonTree = JSON.stringify(folders)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,42 +84,78 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
}: QuartzComponentProps) => {
|
}: QuartzComponentProps) => {
|
||||||
if (ctx.buildId !== lastBuildId) {
|
if (ctx.buildId !== lastBuildId) {
|
||||||
lastBuildId = ctx.buildId
|
lastBuildId = ctx.buildId
|
||||||
constructFileTree(allFiles)
|
constructFileTree(allFiles, (fileData.filePath ?? "").replaceAll(" ", "-"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={classNames(displayClass, "explorer")}>
|
<div class="explorer-container">
|
||||||
<button
|
<div class={`mobile-explorer explorer ${displayClass ?? ""}`}>
|
||||||
type="button"
|
<button
|
||||||
id="explorer"
|
type="button"
|
||||||
data-behavior={opts.folderClickBehavior}
|
id="mobile-explorer"
|
||||||
data-collapsed={opts.folderDefaultState}
|
class="collapsed"
|
||||||
data-savestate={opts.useSavedState}
|
data-behavior={opts.folderClickBehavior}
|
||||||
data-tree={jsonTree}
|
data-collapsed={opts.folderDefaultState}
|
||||||
aria-controls="explorer-content"
|
data-savestate={opts.useSavedState}
|
||||||
aria-expanded={opts.folderDefaultState === "open"}
|
data-pagepathstate={opts.usePagePath}
|
||||||
>
|
data-tree={jsonTree}
|
||||||
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
|
data-mobile={true}
|
||||||
<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"
|
||||||
</button>
|
width="24"
|
||||||
<div id="explorer-content">
|
height="24"
|
||||||
<ul class="overflow" id="explorer-ul">
|
viewBox="0 0 24 24"
|
||||||
<ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
|
stroke-width="2"
|
||||||
<li id="explorer-end" />
|
stroke-linecap="round"
|
||||||
</ul>
|
stroke-linejoin="round"
|
||||||
|
class="lucide 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" class="collapsed">
|
||||||
|
<ul class="overflow" id="explorer-ul">
|
||||||
|
<ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
|
||||||
|
<li id="explorer-end" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={`desktop-explorer explorer ${displayClass ?? ""}`}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="desktop-explorer"
|
||||||
|
class="title-button"
|
||||||
|
data-behavior={opts.folderClickBehavior}
|
||||||
|
data-collapsed={opts.folderDefaultState}
|
||||||
|
data-savestate={opts.useSavedState}
|
||||||
|
data-pagepathstate={opts.usePagePath}
|
||||||
|
data-tree={jsonTree}
|
||||||
|
data-mobile={false}
|
||||||
|
>
|
||||||
|
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
|
||||||
|
<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>
|
||||||
|
</button>
|
||||||
|
<div id="explorer-content" class="">
|
||||||
|
<ul class="overflow" id="explorer-ul">
|
||||||
|
<ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
|
||||||
|
<li id="explorer-end" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -16,6 +16,7 @@ export interface Options {
|
|||||||
folderDefaultState: "collapsed" | "open"
|
folderDefaultState: "collapsed" | "open"
|
||||||
folderClickBehavior: "collapse" | "link"
|
folderClickBehavior: "collapse" | "link"
|
||||||
useSavedState: boolean
|
useSavedState: boolean
|
||||||
|
usePagePath: boolean
|
||||||
sortFn: (a: FileNode, b: FileNode) => number
|
sortFn: (a: FileNode, b: FileNode) => number
|
||||||
filterFn: (node: FileNode) => boolean
|
filterFn: (node: FileNode) => boolean
|
||||||
mapFn: (node: FileNode) => void
|
mapFn: (node: FileNode) => void
|
||||||
@ -124,9 +125,10 @@ export class FileNode {
|
|||||||
* Get folder representation with state of tree.
|
* Get folder representation with state of tree.
|
||||||
* Intended to only be called on root node before changes to the tree are made
|
* Intended to only be called on root node before changes to the tree are made
|
||||||
* @param collapsed default state of folders (collapsed by default or not)
|
* @param collapsed default state of folders (collapsed by default or not)
|
||||||
|
* @param currentFile current file
|
||||||
* @returns array containing folder state for tree
|
* @returns array containing folder state for tree
|
||||||
*/
|
*/
|
||||||
getFolderPaths(collapsed: boolean): FolderState[] {
|
getFolderPaths(collapsed: boolean, currentFile: string): FolderState[] {
|
||||||
const folderPaths: FolderState[] = []
|
const folderPaths: FolderState[] = []
|
||||||
|
|
||||||
const traverse = (node: FileNode, currentPath: string) => {
|
const traverse = (node: FileNode, currentPath: string) => {
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { FolderState } from "../ExplorerNode"
|
import { FolderState } from "../ExplorerNode"
|
||||||
|
|
||||||
|
// Current state of folders
|
||||||
type MaybeHTMLElement = HTMLElement | undefined
|
type MaybeHTMLElement = HTMLElement | undefined
|
||||||
let currentExplorerState: FolderState[]
|
let currentExplorerState: FolderState[]
|
||||||
|
|
||||||
|
function escapeCharacters(str: string) {
|
||||||
|
return str.replace(/'/g, "\\'").replace(/"/g, '\\"')
|
||||||
|
}
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = new IntersectionObserver((entries) => {
|
||||||
// If last element is observed, remove gradient of "overflow" class so element is visible
|
// If last element is observed, remove gradient of "overflow" class so element is visible
|
||||||
const explorerUl = document.getElementById("explorer-ul")
|
const explorerUl = document.getElementById("explorer-ul")
|
||||||
@ -15,24 +21,17 @@ const observer = new IntersectionObserver((entries) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function toggleExplorer(this: HTMLElement) {
|
|
||||||
this.classList.toggle("collapsed")
|
|
||||||
this.setAttribute(
|
|
||||||
"aria-expanded",
|
|
||||||
this.getAttribute("aria-expanded") === "true" ? "false" : "true",
|
|
||||||
)
|
|
||||||
const content = this.nextElementSibling as MaybeHTMLElement
|
|
||||||
if (!content) return
|
|
||||||
|
|
||||||
content.classList.toggle("collapsed")
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleFolder(evt: MouseEvent) {
|
function toggleFolder(evt: MouseEvent) {
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
|
|
||||||
|
// Element that was clicked
|
||||||
const target = evt.target as MaybeHTMLElement
|
const target = evt.target as MaybeHTMLElement
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
|
||||||
|
// Check if target was svg icon or button
|
||||||
const isSvg = target.nodeName === "svg"
|
const isSvg = target.nodeName === "svg"
|
||||||
|
|
||||||
|
// corresponding <ul> element relative to clicked button/folder
|
||||||
const childFolderContainer = (
|
const childFolderContainer = (
|
||||||
isSvg
|
isSvg
|
||||||
? target.parentElement?.nextSibling
|
? target.parentElement?.nextSibling
|
||||||
@ -42,68 +41,150 @@ function toggleFolder(evt: MouseEvent) {
|
|||||||
isSvg ? target.nextElementSibling : target.parentElement
|
isSvg ? target.nextElementSibling : target.parentElement
|
||||||
) as MaybeHTMLElement
|
) as MaybeHTMLElement
|
||||||
if (!(childFolderContainer && currentFolderParent)) return
|
if (!(childFolderContainer && currentFolderParent)) return
|
||||||
|
// <li> element of folder (stores folder-path dataset)
|
||||||
childFolderContainer.classList.toggle("open")
|
childFolderContainer.classList.toggle("open")
|
||||||
|
|
||||||
|
// Collapse folder container
|
||||||
const isCollapsed = childFolderContainer.classList.contains("open")
|
const isCollapsed = childFolderContainer.classList.contains("open")
|
||||||
setFolderState(childFolderContainer, !isCollapsed)
|
setFolderState(childFolderContainer, !isCollapsed)
|
||||||
|
|
||||||
|
// Save folder state to localStorage
|
||||||
const fullFolderPath = currentFolderParent.dataset.folderpath as string
|
const fullFolderPath = currentFolderParent.dataset.folderpath as string
|
||||||
toggleCollapsedByPath(currentExplorerState, fullFolderPath)
|
toggleCollapsedByPath(currentExplorerState, fullFolderPath)
|
||||||
const stringifiedFileTree = JSON.stringify(currentExplorerState)
|
const stringifiedFileTree = JSON.stringify(currentExplorerState)
|
||||||
localStorage.setItem("fileTree", stringifiedFileTree)
|
localStorage.setItem("fileTree", stringifiedFileTree)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupExplorer() {
|
function toggleExplorer(this: HTMLElement) {
|
||||||
const explorer = document.getElementById("explorer")
|
// Toggle collapsed state of entire explorer
|
||||||
if (!explorer) return
|
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"
|
||||||
|
content.style.maxHeight = content.style.maxHeight === "0px" ? "calc(100vh - 8rem)" : "0px"
|
||||||
|
|
||||||
if (explorer.dataset.behavior === "collapse") {
|
//prevent scroll under
|
||||||
|
if (this.dataset.mobile === "true" && document.querySelector("#mobile-explorer")) {
|
||||||
|
const article = document.querySelectorAll(
|
||||||
|
".popover-hint, footer, .backlinks, .graph, .toc, #progress",
|
||||||
|
)
|
||||||
|
const header = document.querySelector(".page .page-header")
|
||||||
|
if (article)
|
||||||
|
article.forEach((element) => {
|
||||||
|
element.classList.toggle("no-scroll")
|
||||||
|
})
|
||||||
|
if (header) header.classList.toggle("fixed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupExplorer() {
|
||||||
|
// Set click handler for collapsing entire explorer
|
||||||
|
const allExplorers = document.querySelectorAll(".explorer > button") as NodeListOf<HTMLElement>
|
||||||
|
|
||||||
|
for (const explorer of allExplorers) {
|
||||||
|
// Get folder state from local storage
|
||||||
|
const storageTree = localStorage.getItem("fileTree")
|
||||||
|
|
||||||
|
// Convert to bool
|
||||||
|
const useSavedFolderState = explorer?.dataset.savestate === "true"
|
||||||
|
|
||||||
|
if (explorer) {
|
||||||
|
// Get config
|
||||||
|
const collapseBehavior = explorer.dataset.behavior
|
||||||
|
|
||||||
|
// Add click handlers for all folders (click handler on folder "label")
|
||||||
|
if (collapseBehavior === "collapse") {
|
||||||
|
for (const item of document.getElementsByClassName(
|
||||||
|
"folder-button",
|
||||||
|
) as HTMLCollectionOf<HTMLElement>) {
|
||||||
|
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
||||||
|
item.addEventListener("click", toggleFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click handler to main explorer
|
||||||
|
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
||||||
|
explorer.addEventListener("click", toggleExplorer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up click handlers for each folder (click handler on folder "icon")
|
||||||
for (const item of document.getElementsByClassName(
|
for (const item of document.getElementsByClassName(
|
||||||
"folder-button",
|
"folder-icon",
|
||||||
) as HTMLCollectionOf<HTMLElement>) {
|
) as HTMLCollectionOf<HTMLElement>) {
|
||||||
item.addEventListener("click", toggleFolder)
|
item.addEventListener("click", toggleFolder)
|
||||||
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
|
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get folder state from local storage
|
||||||
|
const oldExplorerState: FolderState[] =
|
||||||
|
storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
|
||||||
|
const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
|
||||||
|
//console.log(explorer.dataset.tree)
|
||||||
|
//console.log(explorer.dataset.tree ? JSON.parse(explorer.dataset.tree) : [])
|
||||||
|
const newExplorerState: FolderState[] = explorer.dataset.tree
|
||||||
|
? JSON.parse(explorer.dataset.tree)
|
||||||
|
: []
|
||||||
|
currentExplorerState = []
|
||||||
|
|
||||||
|
for (const { path, collapsed } of newExplorerState) {
|
||||||
|
currentExplorerState.push({ path, collapsed: /*oldIndex.get(path) ?? */ collapsed })
|
||||||
|
}
|
||||||
|
|
||||||
|
currentExplorerState.map((folderState) => {
|
||||||
|
const folderLi = document.querySelector(
|
||||||
|
`[data-folderpath='${folderState.path.replace("'", "-")}']`,
|
||||||
|
) as MaybeHTMLElement
|
||||||
|
const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
|
||||||
|
if (folderUl) {
|
||||||
|
setFolderState(folderUl, folderState.collapsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
explorer.addEventListener("click", toggleExplorer)
|
function toggleExplorerFolders() {
|
||||||
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
const currentFile = (document.querySelector("body")?.getAttribute("data-slug") ?? "").replace(
|
||||||
|
/\/index$/g,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
const listToReplace = document.querySelectorAll(".folder-outer:has(> ul[data-folderul]")
|
||||||
|
|
||||||
// Set up click handlers for each folder (click handler on folder "icon")
|
listToReplace.forEach((element) => {
|
||||||
for (const item of document.getElementsByClassName(
|
if (element.children.length > 0) {
|
||||||
"folder-icon",
|
if (currentFile.includes(element.firstElementChild?.getAttribute("data-folderul") ?? "")) {
|
||||||
) as HTMLCollectionOf<HTMLElement>) {
|
if (!element.classList.contains("open")) {
|
||||||
item.addEventListener("click", toggleFolder)
|
element.classList.add("open")
|
||||||
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get folder state from local storage
|
|
||||||
const storageTree = localStorage.getItem("fileTree")
|
|
||||||
const useSavedFolderState = explorer?.dataset.savestate === "true"
|
|
||||||
const oldExplorerState: FolderState[] =
|
|
||||||
storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
|
|
||||||
const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
|
|
||||||
const newExplorerState: FolderState[] = explorer.dataset.tree
|
|
||||||
? JSON.parse(explorer.dataset.tree)
|
|
||||||
: []
|
|
||||||
currentExplorerState = []
|
|
||||||
for (const { path, collapsed } of newExplorerState) {
|
|
||||||
currentExplorerState.push({ path, collapsed: oldIndex.get(path) ?? collapsed })
|
|
||||||
}
|
|
||||||
|
|
||||||
currentExplorerState.map((folderState) => {
|
|
||||||
const folderLi = document.querySelector(
|
|
||||||
`[data-folderpath='${folderState.path}']`,
|
|
||||||
) as MaybeHTMLElement
|
|
||||||
const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
|
|
||||||
if (folderUl) {
|
|
||||||
setFolderState(folderUl, folderState.collapsed)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", setupExplorer)
|
window.addEventListener("resize", setupExplorer)
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const explorer = document.querySelector("#mobile-explorer")
|
||||||
|
if (explorer) {
|
||||||
|
explorer.classList.add("collapsed")
|
||||||
|
const content = explorer.nextElementSibling as HTMLElement
|
||||||
|
content.classList.add("collapsed")
|
||||||
|
content.style.maxHeight = "0px"
|
||||||
|
}
|
||||||
|
toggleExplorerFolders()
|
||||||
|
})
|
||||||
|
|
||||||
document.addEventListener("nav", () => {
|
document.addEventListener("nav", () => {
|
||||||
|
const explorer = document.querySelector("#mobile-explorer")
|
||||||
|
if (explorer) {
|
||||||
|
explorer.classList.add("collapsed")
|
||||||
|
const content = explorer.nextElementSibling as HTMLElement
|
||||||
|
content.classList.add("collapsed")
|
||||||
|
content.style.maxHeight = "0px"
|
||||||
|
}
|
||||||
setupExplorer()
|
setupExplorer()
|
||||||
|
//add collapsed class to all folders
|
||||||
|
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
|
|
||||||
// select pseudo element at end of list
|
// select pseudo element at end of list
|
||||||
@ -111,6 +192,8 @@ document.addEventListener("nav", () => {
|
|||||||
if (lastItem) {
|
if (lastItem) {
|
||||||
observer.observe(lastItem)
|
observer.observe(lastItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleExplorerFolders()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,15 +1,31 @@
|
|||||||
@use "../../styles/variables.scss" as *;
|
@use "../../styles/variables.scss" as *;
|
||||||
|
|
||||||
.explorer {
|
.explorer-container {
|
||||||
display: flex;
|
.mobile-explorer {
|
||||||
flex-direction: column;
|
display: none;
|
||||||
overflow-y: hidden;
|
}
|
||||||
&.desktop-only {
|
.desktop-explorer {
|
||||||
@media all and not ($mobile) {
|
display: flex;
|
||||||
|
}
|
||||||
|
@media all and ($mobile) {
|
||||||
|
.mobile-explorer {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.desktop-explorer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*&:after {
|
|
||||||
|
.mobile-explorer,
|
||||||
|
.desktop-explorer {
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: hidden;
|
||||||
|
&.desktop-only {
|
||||||
|
@media all and not ($mobile) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*&:after {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
content: "";
|
content: "";
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -21,161 +37,234 @@
|
|||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
background: linear-gradient(transparent 0px, var(--light));
|
background: linear-gradient(transparent 0px, var(--light));
|
||||||
}*/
|
}*/
|
||||||
}
|
|
||||||
|
|
||||||
button#explorer {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--dark);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
& h2 {
|
|
||||||
font-size: 1rem;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& .fold {
|
button#mobile-explorer,
|
||||||
margin-left: 0.5rem;
|
button#desktop-explorer {
|
||||||
transition: transform 0.3s ease;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsed .fold {
|
|
||||||
transform: rotateZ(-90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-outer {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 0fr;
|
|
||||||
transition: grid-template-rows 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-outer.open {
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-outer > ul {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#explorer-content {
|
|
||||||
list-style: none;
|
|
||||||
overflow: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
transition:
|
|
||||||
max-height 0.35s ease,
|
|
||||||
visibility 0s linear 0s;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
visibility: visible;
|
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
max-height: 0;
|
|
||||||
transition:
|
|
||||||
max-height 0.35s ease,
|
|
||||||
visibility 0s linear 0.35s;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
& ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0.08rem 0;
|
|
||||||
padding: 0;
|
|
||||||
transition:
|
|
||||||
max-height 0.35s ease,
|
|
||||||
transform 0.35s ease,
|
|
||||||
opacity 0.2s ease;
|
|
||||||
& li > a {
|
|
||||||
color: var(--dark);
|
|
||||||
opacity: 0.75;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> #explorer-ul {
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
& > polyline {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-container {
|
|
||||||
flex-direction: row;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
& div > a {
|
|
||||||
color: var(--secondary);
|
|
||||||
font-family: var(--headerFont);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: $semiBoldWeight;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
& div > a:hover {
|
|
||||||
color: var(--tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
& div > button {
|
|
||||||
color: var(--dark);
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-left: 0;
|
padding: 0;
|
||||||
padding-right: 0;
|
color: var(--dark);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-family: var(--headerFont);
|
|
||||||
|
|
||||||
& span {
|
& h2 {
|
||||||
font-size: 0.95rem;
|
font-size: 1rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: var(--secondary);
|
|
||||||
font-weight: $semiBoldWeight;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1.5rem;
|
}
|
||||||
|
|
||||||
|
& .fold {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed .fold {
|
||||||
|
transform: rotateZ(-90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-outer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
transition: grid-template-rows 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-outer.open {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-outer > ul {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#explorer-content {
|
||||||
|
list-style: none;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
transition:
|
||||||
|
max-height 0.35s ease,
|
||||||
|
visibility 0s linear 0s;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
visibility: visible;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
max-height: 0;
|
||||||
|
transition:
|
||||||
|
max-height 0.35s ease,
|
||||||
|
visibility 0s linear 0.35s;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
& ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0.08rem 0;
|
||||||
|
padding: 0;
|
||||||
|
transition:
|
||||||
|
max-height 0.35s ease,
|
||||||
|
transform 0.35s ease,
|
||||||
|
opacity 0.2s ease;
|
||||||
|
& li > a {
|
||||||
|
color: var(--dark);
|
||||||
|
opacity: 0.75;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> #explorer-ul {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
& > polyline {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.folder-icon {
|
.folder-container {
|
||||||
margin-right: 5px;
|
flex-direction: row;
|
||||||
color: var(--secondary);
|
display: flex;
|
||||||
cursor: pointer;
|
align-items: center;
|
||||||
transition: transform 0.3s ease;
|
user-select: none;
|
||||||
backface-visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
& div > a {
|
||||||
transform: rotate(-90deg);
|
color: var(--secondary);
|
||||||
}
|
font-family: var(--headerFont);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: $semiBoldWeight;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.folder-icon:hover {
|
& div > a:hover {
|
||||||
color: var(--tertiary);
|
color: var(--tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-background::after {
|
& div > button {
|
||||||
background: none !important;
|
color: var(--dark);
|
||||||
}
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-family: var(--headerFont);
|
||||||
|
|
||||||
#explorer-end {
|
& span {
|
||||||
// needs height so IntersectionObserver gets triggered
|
font-size: 0.95rem;
|
||||||
height: 4px;
|
display: inline-block;
|
||||||
// remove default margin from li
|
color: var(--secondary);
|
||||||
margin: 0;
|
font-weight: $semiBoldWeight;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
color: var(--secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
backface-visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon:hover {
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-background::after {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#explorer-end {
|
||||||
|
// needs height so IntersectionObserver gets triggered
|
||||||
|
height: 4px;
|
||||||
|
// remove default margin from li
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-explorer,
|
||||||
|
.desktop-explorer {
|
||||||
|
#explorer-content {
|
||||||
|
//left: 0;
|
||||||
|
z-index: 100;
|
||||||
|
position: absolute;
|
||||||
|
//max-height: max-content !important;
|
||||||
|
background-color: var(--light);
|
||||||
|
//max-width: calc(100% - 36px);
|
||||||
|
max-width: 100%;
|
||||||
|
width: calc(100% - 36px + 4px);
|
||||||
|
//margin-right: 36px;
|
||||||
|
transition: all 300ms ease-in-out;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: calc(100dvh - 8rem);
|
||||||
|
//border-top-left-radius: 23px;
|
||||||
|
//border-top-right-radius: 23px;
|
||||||
|
border-radius: 23px;
|
||||||
|
margin-top: 36px;
|
||||||
|
|
||||||
|
&:not(.collapsed) {
|
||||||
|
height: calc(100dvh - 8rem);
|
||||||
|
//top: 10dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.overflow {
|
||||||
|
max-height: calc(100dvh - 8rem);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mobile-explorer,
|
||||||
|
#desktop-explorer {
|
||||||
|
&:not(.collapsed) .lucide-menu {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transition: transform 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
.lucide-menu {
|
||||||
|
stroke: var(--darkgray);
|
||||||
|
transition: transform 200ms ease;
|
||||||
|
&:hover {
|
||||||
|
stroke: var(--dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-scroll {
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
html:has(.no-scroll) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and not ($mobile) {
|
||||||
|
.no-scroll {
|
||||||
|
opacity: 1 !important;
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
|
html:has(.no-scroll) {
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user