rewrite nav

This commit is contained in:
outfoxxed 2024-10-24 16:26:48 -07:00
parent 6249a0aba7
commit 5341fe58d0
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
18 changed files with 254 additions and 415 deletions

View file

@ -1,5 +1,5 @@
--- ---
import "@styles/components/accordion.css" import "@styles/components/accordion.css";
--- ---
<details class=`accordion ${Astro.props.class ?? ""}` {...Astro.props}> <details class=`accordion ${Astro.props.class ?? ""}` {...Astro.props}>
<summary> <summary>
@ -19,12 +19,13 @@ import "@styles/components/accordion.css"
const body = accordion.querySelector(".accordion-container") as HTMLDivElement; const body = accordion.querySelector(".accordion-container") as HTMLDivElement;
summary.addEventListener("click", event => { summary.addEventListener("click", event => {
if ((event.target as Element).tagName === "A") return;
event.preventDefault(); event.preventDefault();
body.classList.toggle("animate", true);
if (!accordion.open || accordion.classList.contains("closing")) { if (!accordion.open || accordion.classList.contains("closing")) {
accordion.classList.toggle("closing", false); accordion.classList.toggle("closing", false);
body.style.setProperty("--height", "0px"); body.style.setProperty("--height", "0px");
body.classList.toggle("animate", true);
requestAnimationFrame(() => { requestAnimationFrame(() => {
accordion.open = true; accordion.open = true;
@ -34,8 +35,12 @@ import "@styles/components/accordion.css"
body.style.setProperty("--height", body.scrollHeight + "px"); body.style.setProperty("--height", body.scrollHeight + "px");
requestAnimationFrame(() => { requestAnimationFrame(() => {
accordion.classList.toggle("closing", true); body.classList.toggle("animate", true);
body.style.setProperty("--height", "0px");
requestAnimationFrame(() => {
accordion.classList.toggle("closing", true);
body.style.setProperty("--height", "0px");
});
}); });
} }
}); });

View file

@ -1,9 +1,9 @@
--- ---
import Accordion from "./Accordion.astro" import Accordion from "./Accordion.astro";
import collapsibleMarker from "@icons/collapsible-marker.svg?raw" import collapsibleMarker from "@icons/collapsible-marker.svg?raw";
interface Props { interface Props {
title: string title: string;
} }
const { title } = Astro.props; const { title } = Astro.props;
--- ---

View file

@ -0,0 +1,20 @@
---
import Accordion from "./Accordion.astro";
import navMarker from "@icons/nav-marker.svg?raw";
interface Props {
title: string;
link: string;
current?: boolean;
}
const { title, link, current } = Astro.props;
---
<Accordion class=`nav-component nav-collapsible ${current ? "nav-current" : ""}` {...(current ? { open: "" } : {})}>
<div slot="header">
<a class=`nav-link ${current ? "nav-current" : ""}` href={link}>{title}</a>
<div class="nav-collapse-marker">
<Fragment set:html={navMarker}/>
</div>
</div>
<slot>
</Accordion>

View file

@ -1,31 +1,24 @@
--- ---
import { generateTypeData } from "@config/io/generateTypeData"; import SidebarWrapper from "./nav/SidebarWrapper.tsx";
import { groupRoutes } from "@config/io/helpers"; import RootNav from "./nav/RootNav.astro";
import NavComponent from "./nav";
const routes = await generateTypeData();
const groupedRoutes = groupRoutes(routes);
const url = Astro.url.pathname.split("/"); const url = Astro.url.pathname.split("/");
const currentRoute = url[2]; const currentRoute = url[2];
const currentModule = url[3]; const currentModule = url[3];
const currentClass = url[4]; const currentClass = url[4];
const treeProps = { export interface Props {
items: groupedRoutes, mobile: boolean;
currentRoute: currentRoute, }
currentModule: currentModule,
currentClass: currentClass,
};
const { mobile } = Astro.props; const { mobile } = Astro.props;
--- ---
<aside class=`nav-wrapper${mobile ? "-mobile" : ""}`> <aside class=`nav-wrapper${mobile ? "-mobile" : ""}`>
<NavComponent { mobile ? (
routes={groupedRoutes} <SidebarWrapper client:load>
tree={treeProps} <RootNav currentRoute={currentRoute} currentModule={currentModule} currentClass={currentClass}/>
mobile={mobile} </SidebarWrapper>
client:idle ) : (
/> <RootNav currentRoute={currentRoute} currentModule={currentModule} currentClass={currentClass}/>
)}
</aside> </aside>

View file

@ -0,0 +1,10 @@
---
interface Props {
title: string;
link: string;
current?: boolean;
}
const { title, link, current } = Astro.props;
---
<a class=`nav-component nav-item nav-link ${current ? "nav-current" : ""}` href={link}>{title}</a>

View file

@ -0,0 +1,51 @@
---
export interface Props {
currentRoute: string;
currentModule: string;
currentClass: string;
}
const { currentRoute, currentModule, currentClass } = Astro.props;
import { generateTypeData } from "@config/io/generateTypeData";
import { groupRoutes } from "@config/io/helpers";
import Tree from "./Tree.astro";
const routes = await generateTypeData();
const groupedRoutes = groupRoutes(routes);
const configuration = {
title: "Configuration",
link: "/docs/configuration",
current: currentRoute.startsWith("configuration"),
entries: groupedRoutes.tutorials.configuration.map(
({ name, type }) => ({
title: name,
link: `/docs/configuration/${type}`,
current: currentModule === type,
})
),
};
const types = {
title: "Quickshell Types",
link: "/docs/types",
current: currentRoute.startsWith("types"),
entries: Object.entries(groupedRoutes.types).map(
([module, items]) => ({
title: module,
link: `/docs/types/${module}`,
current: currentModule === module,
entries: items.map(type => ({
title: type.name,
link: `/docs/types/${module}/${type.name}`,
current: currentClass === type.name,
})),
})
),
};
---
<nav class="navtree">
<Tree {...configuration}/>
<Tree {...types}/>
</nav>

View file

@ -4,37 +4,25 @@ import {
onMount, onMount,
onCleanup, onCleanup,
type Component, type Component,
type JSXElement,
} from "solid-js"; } from "solid-js";
import { LoadingSpinner, MenuToX, XToMenu } from "@icons"; import { MenuToX, XToMenu } from "@icons";
import { Tree } from "./Tree";
import type { NavProps } from "../types";
const NavComponent: Component<NavProps> = props => { export interface SidebarContent {
children: JSXElement;
}
const NavComponent: Component<SidebarContent> = props => {
const [open, setOpen] = createSignal<boolean>(false); const [open, setOpen] = createSignal<boolean>(false);
const { tree, mobile, routes } = props; const { children } = props;
let navRef: HTMLDivElement; let navRef: HTMLDivElement;
if (!tree) {
return <LoadingSpinner />;
}
function toggle(e: MouseEvent) { function toggle(e: MouseEvent) {
e.preventDefault(); e.preventDefault();
setOpen(!open()); setOpen(!open());
} }
if (!mobile) {
return (
<Tree
currentRoute={tree.currentRoute}
currentModule={tree.currentModule || null}
currentClass={tree.currentClass || null}
items={routes}
/>
);
}
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
const isLink = "href" in (event.target || {}); const isLink = "href" in (event.target || {});
const isInBody = document.body.contains(event.target as Node); const isInBody = document.body.contains(event.target as Node);
@ -78,12 +66,7 @@ const NavComponent: Component<NavProps> = props => {
id={open() ? "#qs_search" : ""} id={open() ? "#qs_search" : ""}
class={`nav-items ${open() ? "shown" : ""}`} class={`nav-items ${open() ? "shown" : ""}`}
> >
<Tree {children}
currentRoute={tree.currentRoute}
currentModule={tree.currentModule}
currentClass={tree.currentClass}
items={routes}
/>
</div> </div>
</div> </div>
); );

View file

@ -0,0 +1,23 @@
---
import NavCollapsible from "@components/NavCollapsible.astro";
import Self from "./Tree.astro";
import Link from "./Link.astro";
interface TreeEntry {
title: string;
link: string;
current?: boolean;
entries?: TreeEntry[];
}
interface Props extends TreeEntry {}
const { title, link, entries, current } = Astro.props;
---
<NavCollapsible title={title} link={link} current={current ?? false}>
{entries?.map(entry => entry.entries ? (
<Self {...entry}/>
) : (
<Link title={entry.title} link={entry.link} current={entry.current}/>
))}
</NavCollapsible>

View file

@ -1,176 +0,0 @@
import { type Component, Index, For } from "solid-js";
import { Accordion } from "@ark-ui/solid";
import { LinkSimple, ShevronSmallDown } from "@icons";
import type { TreeProps } from "../types";
export const Tree: Component<TreeProps> = props => {
const { currentRoute, currentModule, currentClass, items } =
props;
const typeKeys = items!.types && Object.keys(items!.types);
const tutorials =
items!.tutorials && items!.tutorials
? items!.tutorials.configuration
: null;
return (
<nav class="navtree">
<Accordion.Root
defaultValue={
currentRoute === "types" ? ["Types"] : ["Configuration"]
}
collapsible
multiple
>
<Accordion.Item value={"Configuration"}>
<Accordion.ItemTrigger>
<Accordion.ItemIndicator>
<ShevronSmallDown class={"nav-shevron"} />
</Accordion.ItemIndicator>
<p>
<a href={"/docs/configuration"}>Configuration</a>
</p>
</Accordion.ItemTrigger>
<Accordion.ItemContent>
<For each={tutorials}>
{item => (
<div
class={`arktree-item ${currentModule === item.type ? "__current-type-doc" : ""}`}
>
<a href={`/docs/configuration/${item.type}`}>
{item.name}
</a>
</div>
)}
</For>
</Accordion.ItemContent>
</Accordion.Item>
<Accordion.Item value={"Types"}>
<Accordion.ItemTrigger>
<Accordion.ItemIndicator>
<ShevronSmallDown class={"nav-shevron"} />
</Accordion.ItemIndicator>
<p>
<a href={"/docs/types"}>Type Definitions</a>
</p>
</Accordion.ItemTrigger>
<Accordion.ItemContent>
<Index each={typeKeys}>
{typeKey => {
return (
<Accordion.Root
defaultValue={
currentModule === typeKey()
? [typeKey()]
: [""]
}
multiple
collapsible
>
<Accordion.Item
value={typeKey()}
id={typeKey()}
class={
typeKey() === currentModule
? "__current-type-doc"
: ""
}
>
<Accordion.ItemTrigger
id={`${typeKey()}:button`}
>
<Accordion.ItemIndicator>
<ShevronSmallDown
class={"nav-shevron"}
/>
</Accordion.ItemIndicator>
<p>
<a href={`/docs/types/${typeKey()}`}>
{typeKey()}
</a>
</p>
</Accordion.ItemTrigger>
<Accordion.ItemContent>
<For each={items!.types[typeKey()]}>
{submodule => (
<div
class={
currentClass === submodule.name
? "__current-type-doc"
: ""
}
>
<a
href={`/docs/types/${submodule.type}/${submodule.name}`}
>
{submodule.name}
</a>
</div>
)}
</For>
</Accordion.ItemContent>
</Accordion.Item>
</Accordion.Root>
);
}}
</Index>
</Accordion.ItemContent>
</Accordion.Item>
<Accordion.Item
value="QtQuick Type Reference"
id="qtquick-reference"
>
<Accordion.ItemTrigger>
<Accordion.ItemIndicator>
<LinkSimple />
</Accordion.ItemIndicator>
<span
class="link-outside"
onMouseDown={() =>
window.open(
"https://doc.qt.io/qt-6/qtquick-qmlmodule.html"
)
}
>
<a
href="https://doc.qt.io/qt-6/qtquick-qmlmodule.html"
target="_blank"
rel="noreferrer"
>
QtQuick Types
</a>
</span>
</Accordion.ItemTrigger>
</Accordion.Item>
<Accordion.Item
value="Quickshell Examples"
id="quickshell-examples"
>
<Accordion.ItemTrigger>
<Accordion.ItemIndicator>
<LinkSimple />
</Accordion.ItemIndicator>
<span
class="link-outside"
onMouseDown={() =>
window.open(
"https://git.outfoxxed.me/outfoxxed/quickshell-examples"
)
}
>
<a
href="https://git.outfoxxed.me/outfoxxed/quickshell-examples"
target="_blank"
rel="noreferrer"
>
Quickshell Examples
</a>
</span>
</Accordion.ItemTrigger>
</Accordion.Item>
</Accordion.Root>
</nav>
);
};

View file

@ -16,12 +16,6 @@ export interface TreeProps {
currentClass: string | null; currentClass: string | null;
} }
export interface NavProps {
routes: GroupedRoutes;
tree: TreeProps;
mobile: boolean;
}
// Right // Right
export interface TOCProps { export interface TOCProps {
config?: ConfigHeading[]; config?: ConfigHeading[];

View file

@ -1,8 +1,8 @@
--- ---
import { processMarkdown } from "@config/io/markdown" import { processMarkdown } from "@config/io/markdown";
export interface Props { export interface Props {
markdown?: string, markdown?: string;
} }
const { markdown } = Astro.props; const { markdown } = Astro.props;

1
src/icons/nav-marker.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g transform="rotate(-90 12 12)"><path stroke="currentColor" stroke-dasharray="8" stroke-dashoffset="0" stroke-linecap="round" stroke-width="2" d="M9 12L14 7M9 12L14 17" fill="currentColor"></path></g></svg>

After

Width:  |  Height:  |  Size: 293 B

View file

@ -7,6 +7,7 @@
& .accordion-container { & .accordion-container {
/* fixes jumps due to margins on inline items */ /* fixes jumps due to margins on inline items */
display: flex; display: flex;
flex-direction: column;
} }
& .accordion-container.animate { & .accordion-container.animate {

View file

@ -53,8 +53,10 @@ html {
--var-link-color: 190 85 60; --var-link-color: 190 85 60;
--inner-param-color: 215 80 27; --inner-param-color: 215 80 27;
--inner-param-border-color: 215 50 50; --inner-param-border-color: 215 50 50;
--nav-hovered-bkg: var(--blue) 100 93; --nav-hovered-bkg: var(--blue) 100 92;
--nav-hovered-weak-bkg: var(--blue) 100 93;
--nav-selected-bkg: var(--blue) 100 90; --nav-selected-bkg: var(--blue) 100 90;
--nav-selected-hovered-bkg: var(--blue) 100 85;
--nav-selected-text: var(--blue) 60 60; --nav-selected-text: var(--blue) 60 60;
--nav-indicator-bkg: var(--blue) 45 80; --nav-indicator-bkg: var(--blue) 45 80;
--toc-hovered-bkg: 0 0 0 / 0.1; --toc-hovered-bkg: 0 0 0 / 0.1;
@ -106,7 +108,9 @@ html.dark {
--inner-param-border-color: 215 26 46; --inner-param-border-color: 215 26 46;
--inner-param-color: 215 60 70; --inner-param-color: 215 60 70;
--nav-hovered-bkg: var(--blue) 40 10; --nav-hovered-bkg: var(--blue) 40 10;
--nav-hovered-weak-bkg: var(--blue) 35 8;
--nav-selected-bkg: var(--blue) 40 13; --nav-selected-bkg: var(--blue) 40 13;
--nav-selected-hovered-bkg: var(--blue) 40 17;
--nav-selected-text: var(--blue) 100 70; --nav-selected-text: var(--blue) 100 70;
--nav-indicator-bkg: var(--blue) 30 30; --nav-indicator-bkg: var(--blue) 30 30;
--toc-hovered-bkg: 0 0 100 / 0.07; --toc-hovered-bkg: 0 0 100 / 0.07;

View file

@ -27,7 +27,7 @@ pre {
overflow: auto; overflow: auto;
text-wrap: wrap; text-wrap: wrap;
&>button { & > button {
all: unset; all: unset;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;

View file

@ -186,6 +186,8 @@ ul {
cursor: pointer; cursor: pointer;
} }
& div > svg { transform: rotate(90deg) } & div > svg {
transform: rotate(90deg);
}
} }
} }

View file

@ -1,117 +1,108 @@
[data-scope="accordion"][data-part="root"] { .nav-component {
display: flex; margin: 0.35em 0;
flex-direction: column;
gap: 0.15rem;
& [data-part="item"] {
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
} }
[data-scope="accordion"][data-part="item-trigger"] { .nav-link {
background-color: transparent; text-decoration: none !important;
position: relative; }
border: unset;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
width: 100%;
min-height: 2.2rem;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 0.25rem;
.nav-item {
display: block;
border-radius: 6px;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
padding: 0.4em;
&:hover { &:hover {
background-color: hsl(var(--nav-hovered-bkg)); background-color: hsl(var(--nav-hovered-bkg));
} }
}
[data-scope="accordion"][data-part="item-indicator"] { &.nav-current {
position: relative; color: hsl(var(--nav-selected-text));
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
border-radius: 50%;
margin-left: 3px;
transition: transform 250ms ease-in-out;
&:hover {
background-color: hsl(var(--nav-indicator-bkg));
}
&[data-state="open"] {
transform: rotate(180deg);
}
}
.nav-shevron {
color: black;
}
html.dark .nav-shevron {
color: white;
}
[data-scope="accordion"][data-part="item-content"] {
--height: 709;
margin-block: 0.175rem;
margin-left: 1.6rem;
& .arktree-item,
[data-part="item-content"]>div {
display: flex;
flex-direction: column;
gap: 0.15em;
margin: 0.4rem 0;
border-radius: 6px;
transition: background-color 0.2s ease;
&:hover {
background-color: hsl(var(--nav-hovered-bkg));
}
&>a {
padding: 0.4rem;
padding-left: 0.6rem;
width: 100%;
text-decoration: none;
}
}
& .arktree-item.__current-type-doc,
[data-part="item-content"]>div.__current-type-doc {
background-color: hsl(var(--nav-selected-bkg)); background-color: hsl(var(--nav-selected-bkg));
}
}
& a { .nav-collapsible {
overflow: hidden;
& > summary {
user-select: none;
& > div {
& > .nav-collapse-marker,
a {
transition: background-color 0.2s ease;
}
& > .nav-collapse-marker {
border-radius: 0 6px 6px 0;
padding: 0.4em 0.8em;
display: flex;
}
& > a {
border-radius: 6px 0 0 6px;
padding: 0.4em;
flex-grow: 1;
}
&:hover {
& > .nav-collapse-marker,
a {
background-color: hsl(var(--nav-hovered-bkg));
&:not(:hover) {
background-color: hsl(var(--nav-hovered-weak-bkg));
}
}
}
display: flex;
flex-direction: row;
align-items: stretch;
gap: 0.1em;
& svg {
transition: transform 0.3s ease;
font-size: 1.1em;
}
}
}
& .accordion-container > div {
padding-left: 1.2em;
& p:first-child {
padding-top: 0;
margin-top: 0;
}
}
}
.nav-collapsible.nav-current {
& > summary > div {
& > a {
color: hsl(var(--nav-selected-text)); color: hsl(var(--nav-selected-text));
} }
& > .nav-collapse-marker,
a {
background-color: hsl(var(--nav-selected-bkg));
}
&:hover {
& > .nav-collapse-marker,
a {
background-color: hsl(var(--nav-selected-hovered-bkg));
&:not(:hover) {
background-color: hsl(var(--nav-selected-bkg));
}
}
}
} }
} }
[data-scope="accordion"][data-part="item-content"][data-state="open"] { .nav-collapsible[open]:not(.closing) > summary > div svg {
animation: slideDown 250ms ease; transform: rotate(180deg);
}
[data-scope="accordion"][data-part="item-content"][data-state="closed"] {
animation: slideUp 200ms ease;
}
.__current-type-doc {
color: hsl(var(--nav-selected-text));
& [data-part="item-trigger"] a {
color: hsl(var(--nav-selected-text));
}
& [data-scope="accordion"][data-part="item-trigger"] {
background-color: hsl(var(--nav-selected-bkg));
}
} }

View file

@ -37,11 +37,11 @@
position: absolute; position: absolute;
z-index: 11; z-index: 11;
overflow: hidden; overflow: hidden;
top: 2.6rem; top: 2.5rem;
left: calc(-3rem + -80svw); left: -24rem;
height: calc(100svh - 2.5rem); height: calc(100svh - 2.5rem);
font-size: 1rem;
scrollbar-width: none; scrollbar-width: none;
font-size: 1.2rem;
-ms-overflow-style: none; -ms-overflow-style: none;
background: hsla(var(--overlay-bkg)); background: hsla(var(--overlay-bkg));
border-right: 1px solid hsl(var(--overlay-bkg-border)); border-right: 1px solid hsl(var(--overlay-bkg-border));
@ -50,8 +50,7 @@
display: none; display: none;
} }
transition: left 0.3s ease, transition: left 0.3s ease, padding 0.3s ease;
padding 0.3s ease;
&.shown { &.shown {
display: flex; display: flex;
@ -61,47 +60,16 @@
& .navtree { & .navtree {
/* lines up with non overlay nav */ /* lines up with non overlay nav */
padding: 0.5rem 0.618rem; padding: 0rem 0.618rem;
height: 100%; height: 100%;
width: 80svw; width: 21rem;
box-sizing: content-box; box-sizing: content-box;
overflow-y: scroll; overflow-y: scroll;
scrollbar-width: none; scrollbar-width: none;
-ms-overflow-style: none; -ms-overflow-style: none;
margin-bottom: 1rem; margin-bottom: 1rem;
text-wrap: nowrap; text-wrap: nowrap;
& [data-part="item-indicator"] {
margin-left: 1rem;
margin-right: 0.8rem;
height: 2rem;
width: 2rem;
& svg {
height: max-content;
width: 100%;
}
}
& [data-part="item-trigger"] {
min-height: 3em;
font-size: 1.2rem;
}
& [data-part="item-content"] {
margin-left: 3.7rem;
}
& [data-part="item"] {
& [data-part="item-content"]>div {
min-height: 3em;
&>a {
margin: auto 0;
}
}
}
} }
} }
} }
@ -112,45 +80,14 @@
text-align: start; text-align: start;
} }
@media (min-width: 40rem) { @media not (min-width: 40rem) {
.nav-toggle { .nav-toggle .nav-items {
.nav-items { top: 2.6rem;
font-size: 1rem; left: calc(-3rem + -80svw);
top: 2.5rem; font-size: 1.2rem;
left: -24rem;
& .navtree { & .navtree {
width: 21rem; width: 80svw;
}
& .navtree {
& [data-part="item-indicator"] {
margin-left: 0.2rem;
margin-right: 0rem;
height: 1.5rem;
width: 1.5rem;
& svg {
height: unset;
width: unset;
}
}
& [data-part="item-trigger"] {
min-height: 2.2rem;
font-size: 1rem;
}
& [data-part="item-content"] {
margin-left: 1.6rem;
}
& [data-part="item"] {
& [data-part="item-content"]>div {
min-height: 2.2rem;
}
}
}
} }
} }
} }