initial commit

This commit is contained in:
Xanazf 2024-09-28 02:35:19 +03:00
commit 3c2fb32b3e
73 changed files with 22349 additions and 0 deletions

29
.gitignore vendored Normal file
View file

@ -0,0 +1,29 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
.yarn/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
.vscode/
modules/
modules_old/

6
README.md Normal file
View file

@ -0,0 +1,6 @@
# Quickshell Docs
Documentation for [quickshell](https://git.outfoxxed.me/outfoxxed/quickshell)
Hosted version at [quickshell.outfoxxed.me](https://quickshell.outfoxxed.me)
Frontend rewritten by [Xanazf](https://github.com/Xanazf)

41
astro.config.mjs Normal file
View file

@ -0,0 +1,41 @@
import { defineConfig } from "astro/config";
import solidJs from "@astrojs/solid-js";
import { remarkAlert } from "remark-github-blockquote-alert";
import sectionize from "@hbsnow/rehype-sectionize";
import mdx from "@astrojs/mdx";
import pagefind from "astro-pagefind";
// https://astro.build/config
export default defineConfig({
integrations: [
solidJs({
devtools: true,
}),
mdx(),
pagefind(),
],
markdown: {
syntaxHighlight: "shiki",
shikiConfig: {
theme: "material-theme-ocean",
wrap: true,
},
remarkPlugins: [
[
remarkAlert,
{
legacyTitle: true,
},
],
],
rehypePlugins: [
[
sectionize,
{
idPropertyName: "id",
},
],
],
},
});

87
biome.json Normal file
View file

@ -0,0 +1,87 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"formatter": {
"enabled": true,
"formatWithErrors": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 66,
"attributePosition": "multiline"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noVar": "error",
"useConst": "error"
},
"suspicious": {
"noDoubleEquals": "error"
},
"correctness": {
"useJsxKeyInIterable": "info"
}
}
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "es5",
"semicolons": "always",
"arrowParentheses": "asNeeded",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteStyle": "double",
"lineWidth": 66,
"attributePosition": "multiline"
}
},
"overrides": [
{
"include": [
"**/*.ts",
"**/*.tsx"
],
"linter": {
"rules": {
"complexity": {
"noBannedTypes": "off"
},
"correctness": {
"noInvalidUseBeforeDeclaration": "off"
},
"style": {
"noNonNullAssertion": "off"
},
"suspicious": {
"noExplicitAny": "info",
"noDuplicateClassMembers": "off",
"noEmptyBlockStatements": "off"
},
"a11y": {
"noSvgWithoutTitle": "info"
}
}
}
},
{
"include": [
"*.astro"
],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
}
}
}
}
]
}

7728
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

37
package.json Normal file
View file

@ -0,0 +1,37 @@
{
"name": "quickshell-docs",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@ark-ui/solid": "^3.5.0",
"@astrojs/check": "^0.9.3",
"@astrojs/mdx": "^3.1.4",
"@astrojs/solid-js": "^4.4.1",
"@types/node": "^20.14.11",
"astro": "^4.14.5",
"astro-breadcrumbs": "^2.3.1",
"astro-pagefind": "^1.6.0",
"marked": "^14.1.0",
"marked-alert": "^2.0.2",
"node": "npm:22.7.0",
"remark-github-blockquote-alert": "^1.2.1",
"remark-parse": "^11.0.0",
"solid-devtools": "^0.30.1",
"solid-js": "^1.8.18",
"typescript": "^5.5.3"
},
"devDependencies": {
"@astrojs/ts-plugin": "^1.10.1",
"@biomejs/biome": "^1.8.3",
"@hbsnow/rehype-sectionize": "^1.0.7",
"shiki": "^1.11.0"
},
"packageManager": "yarn@4.5.0"
}

1
public/favicon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 100 100"><rect width="100" height="100" rx="20" fill="#24ff70"></rect><path d="M50.41 76.74L41.38 76.74Q40.27 76.74 39.68 76.15Q39.10 75.56 38.75 75.15L38.75 75.15L36.41 72.05Q33.72 72.94 30.27 72.94L30.27 72.94Q23.92 72.94 19.26 70.87Q14.60 68.80 11.95 64.63Q9.29 60.45 9.08 54.17L9.08 54.17Q9.01 51.21 9.01 48.21Q9.01 45.20 9.08 42.24L9.08 42.24Q9.29 36.03 11.98 31.78Q14.67 27.54 19.36 25.40Q24.06 23.26 30.27 23.26L30.27 23.26Q36.54 23.26 41.24 25.40Q45.93 27.54 48.65 31.78Q51.38 36.03 51.52 42.24L51.52 42.24Q51.66 45.20 51.66 48.21Q51.66 51.21 51.52 54.17L51.52 54.17Q51.17 62.73 46.55 67.42L46.55 67.42L51.59 74.60Q51.66 74.67 51.72 74.87Q51.79 75.08 51.79 75.29L51.79 75.29Q51.86 75.84 51.45 76.29Q51.03 76.74 50.41 76.74L50.41 76.74ZM30.27 63.01L30.27 63.01Q33.92 63.01 36.23 60.80Q38.55 58.59 38.68 53.76L38.68 53.76Q38.82 50.72 38.82 48.03Q38.82 45.34 38.68 42.44L38.68 42.44Q38.61 39.20 37.51 37.17Q36.41 35.13 34.58 34.16Q32.75 33.20 30.27 33.20L30.27 33.20Q27.92 33.20 26.06 34.16Q24.19 35.13 23.12 37.17Q22.05 39.20 21.92 42.44L21.92 42.44Q21.85 45.34 21.85 48.03Q21.85 50.72 21.92 53.76L21.92 53.76Q22.12 58.59 24.40 60.80Q26.68 63.01 30.27 63.01ZM73.74 72.94L73.74 72.94Q69.25 72.94 66.11 71.94Q62.97 70.94 61.01 69.49Q59.04 68.04 58.07 66.49Q57.11 64.94 57.04 63.77L57.04 63.77Q56.97 63.01 57.52 62.52Q58.07 62.04 58.69 62.04L58.69 62.04L66.56 62.04Q66.77 62.04 66.94 62.11Q67.11 62.18 67.32 62.39L67.32 62.39Q68.22 62.87 69.11 63.59Q70.01 64.32 71.18 64.87Q72.36 65.42 74.01 65.42L74.01 65.42Q75.94 65.42 77.25 64.63Q78.57 63.83 78.57 62.39L78.57 62.39Q78.57 61.28 77.95 60.56Q77.32 59.83 75.43 59.14Q73.53 58.45 69.73 57.76L69.73 57.76Q66.15 56.93 63.45 55.59Q60.76 54.24 59.31 52.07Q57.87 49.90 57.87 46.79L57.87 46.79Q57.87 44.03 59.66 41.48Q61.45 38.93 64.90 37.30Q68.35 35.68 73.46 35.68L73.46 35.68Q77.39 35.68 80.36 36.61Q83.33 37.55 85.33 38.99Q87.33 40.44 88.36 41.96Q89.40 43.48 89.47 44.72L89.47 44.72Q89.54 45.41 89.05 45.93Q88.57 46.45 87.95 46.45L87.95 46.45L80.77 46.45Q80.50 46.45 80.22 46.34Q79.95 46.24 79.74 46.10L79.74 46.10Q78.91 45.69 78.08 45.00Q77.25 44.31 76.15 43.76Q75.05 43.20 73.39 43.20L73.39 43.20Q71.53 43.20 70.49 44.03Q69.46 44.86 69.46 46.17L69.46 46.17Q69.46 47.07 70.04 47.83Q70.63 48.59 72.46 49.21Q74.29 49.83 78.15 50.59L78.15 50.59Q82.91 51.35 85.74 53.04Q88.57 54.73 89.78 56.97Q90.99 59.21 90.99 61.70L90.99 61.70Q90.99 65.01 88.99 67.56Q86.98 70.11 83.15 71.53Q79.33 72.94 73.74 72.94Z" fill="#fff"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/quickshell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,18 @@
import { Collapsible } from "@ark-ui/solid";
import type { Component, JSX } from "solid-js";
import { CaretCircleRight } from "@icons";
export const DocsCollapsible: Component<{
title: string;
children: JSX.Element;
}> = props => {
return (
<Collapsible.Root>
<Collapsible.Trigger>
<CaretCircleRight />
{props.title}
</Collapsible.Trigger>
<Collapsible.Content>{props.children}</Collapsible.Content>
</Collapsible.Root>
);
};

View file

@ -0,0 +1,66 @@
---
import { ThemeSelect } from "@components/hooks/ThemeSwitch";
import { generateTypeData } from "@config/io/generateTypeData";
import Nav from "@components/navigation/sidebars/Nav.astro";
import TOC from "@components/navigation/sidebars/TOC.astro";
import type { TypeTOC } from "./navigation/sidebars/types";
import Search from "./navigation/Search.astro";
const routes = await generateTypeData();
const url = Astro.url.pathname.split("/");
const currentClass = url[4];
const currentData = routes.find(
item => item.name === currentClass
);
const data = currentData?.data;
const tocFunctions =
data?.functions?.map(item => item.name) || null;
const propsKeys = data?.properties
? Object.keys(data.properties)
: null;
const signalKeys = data?.signals
? Object.keys(data.signals)
: null;
const variantKeys = data?.variants
? Object.keys(data.variants)
: null;
let sidebarData: TypeTOC | undefined = {
properties: propsKeys,
functions: tocFunctions,
signals: signalKeys,
variants: variantKeys,
};
if (!data) {
sidebarData = undefined;
}
const { headings } = Astro.props;
---
<div class="header">
<div class="header-item header-left">
<h3 class="header-title">
<a href="/">Quickshell</a>
</h3>
{url.length > 2 ?
<Nav mobile={true}/>
: null}
<div class="spacer-mobile">|</div>
<h3 class="header-title mobile">
<a href="/">Quickshell</a>
</h3>
</div>
<div class="header-item header-right">
<Search/>
<div class="spacer-desktop">|</div>
<ThemeSelect client:load />
<div class="spacer-mobile">|</div>
{url.length > 2 ?
<TOC headings={headings} types={sidebarData} mobile={true}/>
: null}
</div>
</div>

View file

@ -0,0 +1,30 @@
import {
type ParentComponent,
onMount,
createSignal,
onCleanup,
} from "solid-js";
import { Hashtag } from "@icons";
const MD_Title: ParentComponent<{ titleVar: number }> = props => {
const [_, setMounted] = createSignal<boolean>(false);
onMount(() => {
setMounted(true);
onCleanup(() => {
setMounted(false);
});
});
return (
<div class={`heading heading-${props.titleVar}`}>
<span class="heading-hashtag">
<Hashtag />
</span>
{props.children}
<hr />
</div>
);
};
export default MD_Title;

View file

@ -0,0 +1,52 @@
---
---
<script>
setTimeout(() => {
// code copy
let blocks = document.querySelectorAll("pre");
if (blocks.length > 0) {
blocks.forEach((block) => {
let button = document.createElement("button");
button.className = "copy-button";
button.innerHTML = `<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
>
<path
fill="currentColor"
d="M200 32h-36.26a47.92 47.92 0 0 0-71.48 0H56a16 16 0 0 0-16 16v168a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16m-72 0a32 32 0 0 1 32 32H96a32 32 0 0 1 32-32m72 184H56V48h26.75A47.9 47.9 0 0 0 80 64v8a8 8 0 0 0 8 8h80a8 8 0 0 0 8-8v-8a47.9 47.9 0 0 0-2.75-16H200Z"
/>
</svg>
`;
button.onclick = () => {
let snippet = block.innerText ?? "";
navigator.clipboard.writeText(snippet);
button.classList.toggle("copied");
setTimeout(() => button.classList.remove("copied"), 1000);
};
block.appendChild(button);
});
}
}, 3001)
// heading copy
let headings = document.getElementsByClassName("heading")
if (headings.length > 0) {
for (const heading of headings) {
let button = heading.querySelector("span")
if (button) {
button.onclick = () => {
let link = window.location.href.split("#")[0];
link += `#-${heading.textContent?.slice(10).trimEnd().replaceAll(" ", "-").toLowerCase()}`;
navigator.clipboard.writeText(link);
heading.classList.toggle("copied")
setTimeout(() => heading.classList.remove("copied"), 1000);
}
}
}
}
</script>

View file

@ -0,0 +1,107 @@
---
---
<script>
const qtRegExp = /QT_(\w+)/g
const qsRegExp = /QS_(\w+)/g
setTimeout(() =>{
const blocks = document.querySelectorAll("pre")
if (blocks.length > 0) {
blocks.forEach((block) => {
const content = block.textContent
const elements = block.querySelectorAll("span")
const classElements:HTMLSpanElement[] = [];
if (elements.length === 0) {
console.log("NO SPAN ELEMENTS FOUND")
}
elements.forEach(element => {
const isClassColored = element.style.cssText === "color: rgb(255, 203, 107);"
const isSignal = element.innerText.trim().startsWith("on")
const qualifier = isClassColored && !isSignal
if (qualifier) {
const dotSibling = element.nextSibling
const isSplit = dotSibling?.textContent === "."
if (isSplit) {
let newInnerText = element.innerText + dotSibling.textContent + dotSibling.nextSibling?.textContent
if (dotSibling.nextSibling) {
dotSibling.nextSibling.textContent !== " {"
? dotSibling.nextSibling.remove()
: null
}
dotSibling.remove()
element.innerText = newInnerText
}
classElements.push(element)
}
})
if (content) {
const qtMatch = [...content.matchAll(qtRegExp)]
const qsMatch = [...content.matchAll(qsRegExp)]
if (qtMatch.length > 0) {
for (const qtMatching of qtMatch) {
const newATag = document.createElement("a")
const qtBelongs = qtMatching[0].split("_")[1].replace("11", "-") || null
const qtClass = qtMatching[1].split("_")[1]
const link = `https://doc.qt.io/qt-6/qml-${qtBelongs ? `${qtBelongs}-${qtClass.toLowerCase()}` : "qtquick-" + qtClass.toLowerCase()}.html`
newATag.target = "_blank"
newATag.href = link
newATag.innerText = qtClass
const homeElement = classElements.find(item => {
const spacing = item.innerText.replace(qtMatching[0], "")
if (item.innerText.trim() === qtMatching[0].trim()){
newATag.innerText = spacing + qtClass
return true
}
})
if (homeElement) {
homeElement.innerText = ""
homeElement.appendChild(newATag)
}
}
}
if (qsMatch.length > 0) {
for (const qsMatching of qsMatch) {
const newATag = document.createElement("a")
const qsBelongs = qsMatching[0].split("_")[1].replace("00", ".") || null
const qsClass = qsMatching[1].split("_")[1]
const link = `/docs/types/${qsBelongs ? `${qsBelongs}/${qsClass}` : qsClass}`
newATag.target = "_blank"
newATag.href = link
const homeElement = classElements.find(item => {
const existingItem = item.innerText.trim()
const matchingItem = qsMatching[0].trim()
const spacing = item.innerText.replace(existingItem, "")
if (existingItem === matchingItem) {
newATag.innerText = spacing + qsClass
return true
}
})
if (homeElement) {
homeElement.innerText = ""
if (homeElement.nextSibling) {
homeElement.nextSibling.textContent !== " {"
? homeElement.nextSibling.textContent = ""
: null
}
homeElement.appendChild(newATag)
}
}
}
}
});
}
},3000)
</script>

View file

@ -0,0 +1,29 @@
---
---
<script>
window.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const heading = entry.target.querySelector('h2, h3, h4, h5, h6')
if(heading) {
const id = heading.id
if (entry.intersectionRatio > 0) {
const desktopElement = document.querySelector(`.toc-wrapper li a[href="#${id}"]`);
const mobileElement = document.querySelector(`.toc-wrapper-mobile li a[href="#${id}"]`);
const element = mobileElement?.checkVisibility() ? mobileElement : desktopElement;
element?.parentElement?.classList.add('active')
} else {
const desktopElement = document.querySelector(`.toc-wrapper li a[href="#${id}"]`);
const mobileElement = document.querySelector(`.toc-wrapper-mobile li a[href="#${id}"]`);
const element = mobileElement?.checkVisibility() ? mobileElement : desktopElement;
element?.parentElement?.classList.remove('active')
}
}
});
});
document.querySelectorAll('section[data-heading-rank]').forEach((section) => {
observer.observe(section);
});
});
</script>

View file

@ -0,0 +1,101 @@
import {
createSignal,
createEffect,
onCleanup,
onMount,
type VoidComponent,
} from "solid-js";
import { Sun, Moon } from "@icons";
interface ThemeProps {
theme: "light" | "dark";
system: "light" | "dark";
}
const getCurrentTheme = (): ThemeProps => {
if (
typeof localStorage !== "undefined" &&
(localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches))
) {
return {
theme: "dark",
system: window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light",
};
}
return {
theme: "light",
system: window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light",
};
};
const updateTheme = () => {
document.documentElement.classList.add("changing-theme");
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
requestAnimationFrame(() => {
requestAnimationFrame(() => {
document.documentElement.classList.remove("changing-theme");
});
});
};
export const ThemeSelect: VoidComponent = () => {
const [currentTheme, setCurrentTheme] = createSignal<ThemeProps>({
theme: "dark",
system: "dark",
});
const [mounted, setMounted] = createSignal(false);
const toggleTheme = () => {
if (!mounted()) return;
setCurrentTheme(getCurrentTheme());
if (currentTheme()!.theme !== currentTheme()!.system) {
localStorage.removeItem("theme");
} else {
localStorage.theme = currentTheme()!.theme === "dark" ? "light" : "dark";
}
updateTheme();
setCurrentTheme(getCurrentTheme());
};
onMount(() => {
setMounted(true);
setCurrentTheme(getCurrentTheme());
});
createEffect(() => {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", updateTheme);
window.addEventListener("storage", updateTheme);
onCleanup(() => {
mediaQuery.removeEventListener("change", updateTheme);
window.removeEventListener("storage", updateTheme);
setMounted(false);
});
});
return (
<div onclick={toggleTheme} class="theme-toggle">
{(mounted() && currentTheme().theme === "light") ||
currentTheme().system === "light" ? (
<Sun class="theme-sun" />
) : (
<Moon class="theme-moon" />
)}
</div>
);
};

View file

@ -0,0 +1,66 @@
---
---
<script>
import { getQMLTypeLinkObject, getQMLTypeLink, getIconForLink } from "@config/io/helpers"
const detailsData = document.getElementsByTagName("p")
const innerItems = document.getElementsByClassName("typedata-details")
if (detailsData) {
for (const details of detailsData) {
const linkRegex = /TYPE99(\w+.)99TYPE/g
const mtypeExists = details.textContent?.match(linkRegex)
if (!mtypeExists || !details.textContent) {
continue;
}
const linkMatch = [...details.textContent.matchAll(linkRegex)]
let textWithLinks = details.textContent;
for (const matching of linkMatch) {
if (details.textContent.indexOf(matching[0]) === -1){
continue
}
const linkObject = getQMLTypeLinkObject(matching[1]);
const link = getQMLTypeLink(linkObject);
const icon = linkObject.mtype ? getIconForLink(linkObject.mtype, false) : null;
// for signal
const bracketString = getIconForLink("func", false)
const newLink = `<span class="type${linkObject.mtype}-link typedata-link">${icon ? icon : ""}<a href=${link}>${linkObject.mname || linkObject.name}</a>${linkObject.mtype === "signal" ? bracketString : ""}</span>`;
textWithLinks = textWithLinks.replace(matching[0], newLink)
}
details.innerHTML = textWithLinks
}
}
if (innerItems){
for (const innerItem of innerItems){
const linkRegex = /TYPE99(\w+.)99TYPE/g
const listItems = innerItem.getElementsByTagName("li")
for (const li of listItems){
const mtypeExists = li.textContent?.match(linkRegex)
if (!mtypeExists || !li.textContent){
continue
}
const linkMatch = [...li.textContent.matchAll(linkRegex)]
let textWithLinks = li.textContent;
for (const matching of linkMatch) {
if (li.textContent.indexOf(matching[0]) === -1){
continue
}
const linkObject = getQMLTypeLinkObject(matching[1]);
const link = getQMLTypeLink(linkObject);
const icon = linkObject.mtype ? getIconForLink(linkObject.mtype, false) : null;
// for signal
const bracketString = getIconForLink("func", false)
const newLink = `<span class="type${linkObject.mtype}-link typedata-link">${icon ? icon : ""}<a href=${link}>${linkObject.mname || linkObject.name}</a>${linkObject.mtype === "signal" ? bracketString : ""}</span>`;
textWithLinks = textWithLinks.replace(matching[0], newLink)
}
li.innerHTML = textWithLinks
}
}
}
</script>

View file

@ -0,0 +1,21 @@
---
---
<script>
import { codeToHtml } from "shiki";
const injectedMd = document.getElementById("injectedMd");
if (injectedMd) {
const preElements = document.getElementsByTagName("pre");
for (const pre of preElements) {
if (pre.textContent){
const innerText = pre.innerText;
pre.outerHTML =
await codeToHtml(
innerText,
{
lang: "qml", "theme":"material-theme-ocean"
}
);
}
}
}
</script>

477
src/components/icons.tsx Normal file
View file

@ -0,0 +1,477 @@
import type { VoidComponent } from "solid-js";
export const XToMenu: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
class={props.class}
>
<title>Close Menu</title>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path d="M5 5L12 12L19 5">
<animate
fill="freeze"
attributeName="d"
dur="0.4s"
values="M5 5L12 12L19 5;M5 5L12 5L19 5"
/>
</path>
<path d="M12 12H12">
<animate
fill="freeze"
attributeName="d"
dur="0.4s"
values="M12 12H12;M5 12H19"
/>
</path>
<path d="M5 19L12 12L19 19">
<animate
fill="freeze"
attributeName="d"
dur="0.4s"
values="M5 19L12 12L19 19;M5 19L12 19L19 19"
/>
</path>
</g>
</svg>
);
};
export const MenuToX: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
class={props.class}
>
<title>Open Menu</title>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
>
<path d="M5 5L12 5L19 5">
<animate
fill="freeze"
attributeName="d"
dur="0.4s"
values="M5 5L12 5L19 5;M5 5L12 12L19 5"
/>
</path>
<path d="M5 12H19">
<animate
fill="freeze"
attributeName="d"
dur="0.4s"
values="M5 12H19;M12 12H12"
/>
</path>
<path d="M5 19L12 19L19 19">
<animate
fill="freeze"
attributeName="d"
dur="0.4s"
values="M5 19L12 19L19 19;M5 19L12 12L19 19"
/>
</path>
</g>
</svg>
);
};
export const Sun: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Light</title>
<path
fill="currentColor"
d="M120 40V16a8 8 0 0 1 16 0v24a8 8 0 0 1-16 0m72 88a64 64 0 1 1-64-64a64.07 64.07 0 0 1 64 64m-16 0a48 48 0 1 0-48 48a48.05 48.05 0 0 0 48-48M58.34 69.66a8 8 0 0 0 11.32-11.32l-16-16a8 8 0 0 0-11.32 11.32Zm0 116.68l-16 16a8 8 0 0 0 11.32 11.32l16-16a8 8 0 0 0-11.32-11.32M192 72a8 8 0 0 0 5.66-2.34l16-16a8 8 0 0 0-11.32-11.32l-16 16A8 8 0 0 0 192 72m5.66 114.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32-11.32ZM48 128a8 8 0 0 0-8-8H16a8 8 0 0 0 0 16h24a8 8 0 0 0 8-8m80 80a8 8 0 0 0-8 8v24a8 8 0 0 0 16 0v-24a8 8 0 0 0-8-8m112-88h-24a8 8 0 0 0 0 16h24a8 8 0 0 0 0-16"
/>
</svg>
);
};
export const Moon: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Dark</title>
<path
fill="currentColor"
d="M233.54 142.23a8 8 0 0 0-8-2a88.08 88.08 0 0 1-109.8-109.8a8 8 0 0 0-10-10a104.84 104.84 0 0 0-52.91 37A104 104 0 0 0 136 224a103.1 103.1 0 0 0 62.52-20.88a104.84 104.84 0 0 0 37-52.91a8 8 0 0 0-1.98-7.98m-44.64 48.11A88 88 0 0 1 65.66 67.11a89 89 0 0 1 31.4-26A106 106 0 0 0 96 56a104.11 104.11 0 0 0 104 104a106 106 0 0 0 14.92-1.06a89 89 0 0 1-26.02 31.4"
/>
</svg>
);
};
export const ShevronSmallDown: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
class={props.class}
>
<title>Open</title>
<g transform="rotate(-90 12 12)">
<path
stroke="currentColor"
stroke-dasharray="8"
stroke-dashoffset="8"
stroke-linecap="round"
stroke-width="2"
d="M9 12L14 7M9 12L14 17"
fill="currentColor"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.3s"
values="8;0"
/>
</path>
</g>
</svg>
);
};
export const CaretCircleRight: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Open</title>
<path
fill="currentColor"
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m0 192a88 88 0 1 1 88-88a88.1 88.1 0 0 1-88 88m29.66-93.66a8 8 0 0 1 0 11.32l-40 40a8 8 0 0 1-11.32-11.32L140.69 128l-34.35-34.34a8 8 0 0 1 11.32-11.32Z"
/>
</svg>
);
};
export const Clipboard: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Copy code</title>
<path
fill="currentColor"
d="M200 32h-36.26a47.92 47.92 0 0 0-71.48 0H56a16 16 0 0 0-16 16v168a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16m-72 0a32 32 0 0 1 32 32H96a32 32 0 0 1 32-32m72 184H56V48h26.75A47.9 47.9 0 0 0 80 64v8a8 8 0 0 0 8 8h80a8 8 0 0 0 8-8v-8a47.9 47.9 0 0 0-2.75-16H200Z"
/>
</svg>
);
};
export const Hashtag: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Copy link</title>
<path
fill="currentColor"
d="M224 90h-51l8.89-48.93a6 6 0 1 0-11.8-2.14L160.81 90H109l8.89-48.93a6 6 0 0 0-11.8-2.14L96.81 90H48a6 6 0 0 0 0 12h46.63l-9.46 52H32a6 6 0 0 0 0 12h51l-8.9 48.93a6 6 0 0 0 4.83 7A5.6 5.6 0 0 0 80 222a6 6 0 0 0 5.89-4.93l9.3-51.07H147l-8.89 48.93a6 6 0 0 0 4.83 7a5.6 5.6 0 0 0 1.08.1a6 6 0 0 0 5.89-4.93l9.28-51.1H208a6 6 0 0 0 0-12h-46.63l9.46-52H224a6 6 0 0 0 0-12m-74.83 64h-51.8l9.46-52h51.8Z"
/>
</svg>
);
};
export const Tag: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Go to</title>
<path
fill="currentColor"
d="M246.66 123.56L201 55.13A15.94 15.94 0 0 0 187.72 48H40a16 16 0 0 0-16 16v128a16 16 0 0 0 16 16h147.72a16 16 0 0 0 13.28-7.12l45.63-68.44a8 8 0 0 0 .03-8.88M187.72 192H40V64h147.72l42.66 64Z"
/>
</svg>
);
};
export const Subtitles: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Go to</title>
<path
fill="currentColor"
d="M224 48H32a16 16 0 0 0-16 16v128a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16m0 144H32V64h192zM48 136a8 8 0 0 1 8-8h16a8 8 0 0 1 0 16H56a8 8 0 0 1-8-8m160 0a8 8 0 0 1-8 8h-96a8 8 0 0 1 0-16h96a8 8 0 0 1 8 8m-48 32a8 8 0 0 1-8 8H56a8 8 0 0 1 0-16h96a8 8 0 0 1 8 8m48 0a8 8 0 0 1-8 8h-16a8 8 0 0 1 0-16h16a8 8 0 0 1 8 8"
/>
</svg>
);
};
export const Ruler: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Go to</title>
<path
fill="currentColor"
d="m235.32 73.37l-52.69-52.68a16 16 0 0 0-22.63 0L20.68 160a16 16 0 0 0 0 22.63l52.69 52.68a16 16 0 0 0 22.63 0L235.32 96a16 16 0 0 0 0-22.63M84.68 224L32 171.31l32-32l26.34 26.35a8 8 0 0 0 11.32-11.32L75.31 128L96 107.31l26.34 26.35a8 8 0 0 0 11.32-11.32L107.31 96L128 75.31l26.34 26.35a8 8 0 0 0 11.32-11.32L139.31 64l32-32L224 84.69Z"
/>
</svg>
);
};
export const RoundBrackets: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Go to</title>
<path
fill="currentColor"
d="M40 128c0 58.29 34.67 80.25 36.15 81.16a8 8 0 0 1-8.27 13.7C66.09 221.78 24 195.75 24 128s42.09-93.78 43.88-94.86a8 8 0 0 1 8.26 13.7C74.54 47.83 40 69.82 40 128m148.12-94.86a8 8 0 0 0-8.27 13.7C181.33 47.75 216 69.71 216 128s-34.67 80.25-36.12 81.14a8 8 0 0 0 8.24 13.72C189.91 221.78 232 195.75 232 128s-42.09-93.78-43.88-94.86"
/>
</svg>
);
};
export const PowerCord: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Go to</title>
<path
fill="currentColor"
d="M149.66 138.34a8 8 0 0 0-11.32 0L120 156.69L99.31 136l18.35-18.34a8 8 0 0 0-11.32-11.32L88 124.69l-18.34-18.35a8 8 0 0 0-11.32 11.32l6.35 6.34l-23.32 23.31a32 32 0 0 0 0 45.26l5.38 5.37l-28.41 28.4a8 8 0 0 0 11.32 11.32l28.4-28.41l5.37 5.38a32 32 0 0 0 45.26 0L132 191.31l6.34 6.35a8 8 0 0 0 11.32-11.32L131.31 168l18.35-18.34a8 8 0 0 0 0-11.32m-52.29 65a16 16 0 0 1-22.62 0l-22.06-22.09a16 16 0 0 1 0-22.62L76 135.31L120.69 180Zm140.29-185a8 8 0 0 0-11.32 0l-28.4 28.41l-5.37-5.38a32.05 32.05 0 0 0-45.26 0L124 64.69l-6.34-6.35a8 8 0 0 0-11.32 11.32l80 80a8 8 0 0 0 11.32-11.32l-6.35-6.34l23.32-23.31a32 32 0 0 0 0-45.26l-5.38-5.37l28.41-28.4a8 8 0 0 0 0-11.32m-34.35 79L180 120.69L135.31 76l23.32-23.31a16 16 0 0 1 22.62 0l22.06 22a16 16 0 0 1 0 22.68Z"
/>
</svg>
);
};
export const FourDiamonds: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Go to</title>
<path
fill="currentColor"
d="M122.34 109.66a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32l-40-40a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32ZM128 35.31L156.69 64L128 92.69L99.31 64Zm5.66 111a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32l40 40a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32ZM128 220.69L99.31 192L128 163.31L156.69 192Zm109.66-98.35l-40-40a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32l40 40a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32M192 156.69L163.31 128L192 99.31L220.69 128Zm-82.34-34.35l-40-40a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32l40 40a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32M64 156.69L35.31 128L64 99.31L92.69 128Z"
/>
</svg>
);
};
export const Flag: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Flags</title>
<path
fill="currentColor"
d="M42.76 50A8 8 0 0 0 40 56v168a8 8 0 0 0 16 0v-44.23c26.79-21.16 49.87-9.75 76.45 3.41c16.4 8.11 34.06 16.85 53 16.85c13.93 0 28.54-4.75 43.82-18a8 8 0 0 0 2.76-6V56a8 8 0 0 0-13.27-6c-28 24.23-51.72 12.49-79.21-1.12C111.07 34.76 78.78 18.79 42.76 50M216 172.25c-26.79 21.16-49.87 9.74-76.45-3.41c-25-12.35-52.81-26.13-83.55-8.4V59.79c26.79-21.16 49.87-9.75 76.45 3.4c25 12.35 52.82 26.13 83.55 8.4Z"
/>
</svg>
);
};
export const ReturnKey: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Return</title>
<path
fill="currentColor"
d="M184 104v32a8 8 0 0 1-8 8H99.31l10.35 10.34a8 8 0 0 1-11.32 11.32l-24-24a8 8 0 0 1 0-11.32l24-24a8 8 0 0 1 11.32 11.32L99.31 128H168v-24a8 8 0 0 1 16 0m48-48v144a16 16 0 0 1-16 16H40a16 16 0 0 1-16-16V56a16 16 0 0 1 16-16h176a16 16 0 0 1 16 16m-16 144V56H40v144z"
/>
</svg>
);
};
export const ArrowRightElbow: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Return</title>
<path
fill="currentColor"
d="m221.66 181.66l-48 48a8 8 0 0 1-11.32-11.32L196.69 184H72a8 8 0 0 1-8-8V32a8 8 0 0 1 16 0v136h116.69l-34.35-34.34a8 8 0 0 1 11.32-11.32l48 48a8 8 0 0 1 0 11.32"
/>
</svg>
);
};
export const ArrowLeftSimple: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Return</title>
<path
fill="currentColor"
d="M224 128a8 8 0 0 1-8 8H59.31l58.35 58.34a8 8 0 0 1-11.32 11.32l-72-72a8 8 0 0 1 0-11.32l72-72a8 8 0 0 1 11.32 11.32L59.31 120H216a8 8 0 0 1 8 8"
/>
</svg>
);
};
export const Article: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class={props.class}
>
<title>Types</title>
<path
fill="currentColor"
d="M216 40H40a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16m0 160H40V56h176zM184 96a8 8 0 0 1-8 8H80a8 8 0 0 1 0-16h96a8 8 0 0 1 8 8m0 32a8 8 0 0 1-8 8H80a8 8 0 0 1 0-16h96a8 8 0 0 1 8 8m0 32a8 8 0 0 1-8 8H80a8 8 0 0 1 0-16h96a8 8 0 0 1 8 8"
/>
</svg>
);
};
export const LoadingSpinner: VoidComponent<{
class?: string;
}> = props => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
class={props.class}
>
<title>Loading</title>
<path
fill="currentColor"
d="M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z"
>
<animateTransform
attributeName="transform"
dur="1.8s"
repeatCount="indefinite"
type="rotate"
values="0 12 12;360 12 12"
/>
</path>
</svg>
);
};

View file

@ -0,0 +1,7 @@
---
import SearchComponent from "./search";
---
<div class="search-wrapper">
<SearchComponent client:only={"solid-js"}/>
</div>

View file

@ -0,0 +1,53 @@
import { For, type Component } from "solid-js";
import type { SearchResult } from "./types";
import {
getIconForLink,
getQMLTypeLink,
getQMLTypeLinkObject,
} from "@src/config/io/helpers";
const SearchModal: Component<{
results: SearchResult[];
}> = props => {
const { results } = props;
const linkRegex = /TYPE99(\w+.)99TYPE/g;
return (
<div
id="search-modal"
class="search-output"
>
<For each={results}>
{result => {
let excerpt = result.excerpt;
const linkMatch = [...excerpt.matchAll(linkRegex)];
for (const match of linkMatch) {
const unparsed = match[1];
const linkObject = getQMLTypeLinkObject(unparsed);
const linkParsed = getQMLTypeLink(linkObject);
const icon = linkObject.mtype
? getIconForLink(linkObject.mtype, false)
: "";
const bracketString = getIconForLink("func", false);
const newString = `<span class="type${linkObject.mtype}-link typedata-link">${icon}<a href=${linkParsed}>${linkObject.mname || linkObject.name}</a>${linkObject.mtype === "signal" ? bracketString : ""}</span>`;
excerpt = excerpt.replace(match[0], newString);
}
excerpt = `${excerpt}...`;
return (
<div class="search-output_item">
<h3 class="search-output_heading">
<a href={result.url}>{result.meta.title}</a>
</h3>
<section
class="search-output_excerpt"
innerHTML={excerpt}
/>
</div>
);
}}
</For>
</div>
);
};
export default SearchModal;

View file

@ -0,0 +1,59 @@
import {
createResource,
createSignal,
type Component,
} from "solid-js";
import type { SearchResult } from "./types";
import SearchModal from "./SearchModal";
const pagefind = await import("@dist/pagefind/pagefind.js");
pagefind.init();
async function PagefindSearch(query: string) {
const search = await pagefind.search(query);
const resultdata: SearchResult[] = [];
for (const result of search.results) {
const data = await result.data();
resultdata.push(data);
}
return resultdata;
}
const SearchComponent: Component = () => {
let modal!: HTMLElement;
const [query, setQuery] = createSignal("");
const [results, { refetch }] = createResource(
query,
PagefindSearch
);
function handleSearch(value: string) {
setQuery(value);
refetch();
console.log(results());
}
return (
<div class="search">
<input
id="search-input"
type="text"
role="searchbox"
incremental
value={query()}
placeholder="Search"
onChange={e => handleSearch(e.target.value)}
//onfocusout={() => setQuery("")}
/>{" "}
{!results.loading && results() && results()!.length > 0 ? (
<SearchModal
results={results()!}
ref={modal}
/>
) : null}
</div>
);
};
export default SearchComponent;

View file

@ -0,0 +1,15 @@
interface SearchResult {
url: string;
excerpt: string;
meta: {
title: string;
image?: string;
};
sub_results: {
title: string;
url: string;
excerpt: string;
}[];
}
export type { SearchResult }

View file

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

View file

@ -0,0 +1,21 @@
---
import TableOfContents from "./toc";
import type { ConfigHeading, TypeTOC } from "./types.d.ts";
export interface Props {
headings?: ConfigHeading[];
types?: TypeTOC;
mobile: boolean;
}
const { headings, types, mobile } = Astro.props;
---
<div class=`toc-wrapper${mobile ? "-mobile":""}`>
<TableOfContents
config={headings}
type={types}
mobile={mobile}
client:idle
/>
</div>

View file

@ -0,0 +1,124 @@
import { type Component, Index, For } from "solid-js";
import { Accordion } from "@ark-ui/solid";
import { 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.Root>
</nav>
);
};

View file

@ -0,0 +1,52 @@
import { createSignal, type Component } from "solid-js";
import { LoadingSpinner, MenuToX, XToMenu } from "@icons";
import { Tree } from "./Tree";
import type { NavProps } from "../types";
const NavComponent: Component<NavProps> = props => {
const [open, setOpen] = createSignal<boolean>(false);
const { tree, mobile, routes } = props;
if (!tree) {
return <LoadingSpinner />;
}
function toggle(e: MouseEvent) {
e.preventDefault();
setOpen(!open());
}
if (!mobile) {
return (
<Tree
currentRoute={tree.currentRoute}
currentModule={tree.currentModule || null}
currentClass={tree.currentClass || null}
items={routes}
/>
);
}
return (
<div class="nav-toggle">
<div onclick={e => toggle(e)}>
{open() ? (
<MenuToX class="nav-icon" />
) : (
<XToMenu class="nav-icon" />
)}
</div>
<div class={`nav-items ${open() ? "shown" : ""}`}>
<Tree
currentRoute={tree.currentRoute}
currentModule={tree.currentModule}
currentClass={tree.currentClass}
items={routes}
/>
</div>
</div>
);
};
export default NavComponent;

View file

@ -0,0 +1,33 @@
import { For, type Component } from "solid-js";
import type { ConfigTOC } from "../types";
export const Heading: Component<{
heading: ConfigTOC;
index: number;
}> = props => {
const { heading, index } = props;
return (
<li class={`toc_heading toc_heading-${index}`}>
<a
class="toc_a"
href={`#${heading.slug}`}
>
{heading.text}
</a>
{heading.subheadings.length > 0 && (
<ul>
<For each={heading.subheadings}>
{subheading => (
<Heading
heading={subheading}
index={subheading.depth}
/>
)}
</For>
</ul>
)}
</li>
);
};

View file

@ -0,0 +1,111 @@
import { type Component, For } from "solid-js";
import type { TypeTOC, ConfigTOC } from "../types";
import {
LoadingSpinner,
Tag,
RoundBrackets,
PowerCord,
FourDiamonds,
} from "@icons";
import { Heading } from "./Heading";
export const Table: Component<{
typeTOC?: TypeTOC;
configTOC?: ConfigTOC[];
}> = props => {
const { typeTOC, configTOC } = props;
if (configTOC) {
return (
<div class="toc-content">
<p>Contents</p>
<For each={configTOC}>
{heading => (
<Heading
heading={heading}
index={0}
/>
)}
</For>
</div>
);
}
if (!typeTOC) {
return <LoadingSpinner />;
}
return (
<nav class="toc-content">
{typeTOC.properties ? (
<ul class="types-list props-list">
<For each={typeTOC.properties}>
{prop => (
<li class="types-item props-item">
<Tag />
<a
class="type-anchor"
href={`#${prop}`}
>
{prop}
</a>
</li>
)}
</For>
</ul>
) : null}
{typeTOC.functions ? (
<ul class="types-list funcs-list">
<For each={typeTOC.functions}>
{func => (
<li class="types-item func-item">
<RoundBrackets />
<a
class="type-anchor"
href={`#${func}`}
>
{func}
</a>
</li>
)}
</For>
</ul>
) : null}
{typeTOC.signals ? (
<ul class="types-list signals-list">
<For each={typeTOC.signals}>
{signal => (
<li class="types-item signals-item">
<PowerCord />
<a
class="type-anchor"
href={`#${signal}`}
>
{signal}
</a>
</li>
)}
</For>
</ul>
) : null}
{typeTOC.variants ? (
<ul class="types-list vars-list">
<For each={typeTOC.variants}>
{variant => (
<li class="types-item vars-item">
<FourDiamonds />
<a
class="type-anchor"
href={`#${variant}`}
>
{variant}
</a>
</li>
)}
</For>
</ul>
) : null}
</nav>
);
};

View file

@ -0,0 +1,52 @@
import { createSignal, type Component } from "solid-js";
import { Article } from "@icons";
import { Table } from "./Table";
import type {
TOCProps,
TypeTOC,
ConfigHeading,
} from "../types";
import { buildHierarchy } from "@config/io/helpers";
const TableOfContents: Component<TOCProps> = props => {
const [open, setOpen] = createSignal<boolean>(false);
const [typeProps] = createSignal<TypeTOC | undefined>(
props.type
);
const [configProps] = createSignal<
ConfigHeading[] | undefined
>(props.config);
function toggle(e: MouseEvent) {
e.preventDefault();
setOpen(!open());
}
if (!props.mobile) {
return typeProps() ? (
<Table typeTOC={typeProps()} />
) : (
<Table configTOC={buildHierarchy(configProps()!)} />
);
}
return (
<div class="menu-toggle">
<div onclick={e => toggle(e)}>
<Article />
</div>
<div class={`menu-items ${open() ? "shown" : ""}`}>
{typeProps() ? (
<Table typeTOC={typeProps()} />
) : (
<Table
configTOC={buildHierarchy(configProps()!)}
/>
)}
</div>
</div>
);
};
export default TableOfContents;

View file

@ -0,0 +1,41 @@
import { createSignal, type Component } from "solid-js";
import { Article } from "@icons";
import { Table } from "./Table";
import type { TOCProps } from "../types";
import { buildHierarchy } from "@config/io/helpers";
const TableOfContents: Component<TOCProps> = props => {
const [open, setOpen] = createSignal<boolean>(false);
const { mobile, config, type } = props;
function toggle(e: MouseEvent) {
e.preventDefault();
setOpen(!open());
}
if (!mobile) {
return type ? (
<Table typeTOC={type} />
) : (
<Table configTOC={buildHierarchy(config!)} />
);
}
return (
<div class="toc-toggle">
<div onclick={e => toggle(e)}>
<Article />
</div>
<div class={`toc-mobile ${open() ? "shown" : ""}`}>
{type ? (
<Table typeTOC={type} />
) : (
<Table configTOC={buildHierarchy(config!)} />
)}
</div>
</div>
);
};
export default TableOfContents;

View file

@ -0,0 +1,63 @@
// Left
export interface Item {
name: string;
type: string;
}
export interface GroupedRoutes {
tutorials: { [key: string]: Item[] };
types: { [key: string]: Item[] };
}
export interface TreeProps {
items: GroupedRoutes;
currentRoute?: string;
currentModule: string | null;
currentClass: string | null;
}
export interface NavProps {
routes: GroupedRoutes;
tree: TreeProps;
mobile: boolean;
}
// Right
export interface TOCProps {
config?: ConfigHeading[];
type?: TypeTableProps;
mobile: boolean;
}
// -- Config
export interface ConfigHeading {
slug: string;
text: string;
depth: number;
}
export interface ConfigTOC {
slug: string;
text: string;
depth: number;
subheadings: ConfigTOC[];
}
export interface ConfigTableProps {
content: {
title: string;
};
headings: ConfigHeading[];
frontmatter?: {
title: string;
description: string;
};
}
// -- Types
export interface TypeTOC {
properties: string[] | null;
functions: string[] | null;
signals: string[] | null;
variants: string[] | null;
}

View file

@ -0,0 +1,67 @@
---
import type {
QMLTypeLinkObject,
QuickshellFunction,
} from "@config/io/types";
import {
parseMarkdown,
getQMLTypeLink,
} from "@config/io/helpers";
import { Tag } from "@icons";
export interface Props {
funcData: QuickshellFunction[];
title: string;
}
const { funcData, title } = Astro.props;
---
<ul class="typedata typefuncs">
{
funcData.map(item => {
const functionParams = item.params.length > 0 ? item.params : null
const retTypeLink = getQMLTypeLink(item.ret as unknown as QMLTypeLinkObject)
return (
<li id={item.name} class="typedata-root typefunc-root">
<p class="typedata-name typefunc-name">
{item.name}({functionParams
? functionParams.map((itemType, index) => (
<span class="typedata-param">{itemType.name}{
index !== functionParams.length - 1
&& ", "
}</span>
)
) : null})<span class="type-datatype">:&nbsp;<a
href={retTypeLink}
target="_blank"
>{item.ret.name || item.ret.type}</a></span>
</p>
{
item.params.length > 0 ? (
<p class="typedata-params typefunc-params">
{
item.params.map(param => {
const paramTypeLink = getQMLTypeLink(param.type);
return (
<span class="typedata-param typefunc-param">
<Tag client:idle/>
{param.name}<span class="type-datatype">:&nbsp;<a
href={paramTypeLink}
target="_blank"
>{param.type.name}</a></span>
</span>
)
})
}
</p>
)
:null
}
<section class="typedata-details">
<div class="typedata-detailsdata" set:html={parseMarkdown(item.details, title)}/>
</section>
</li>
)
})
}
</ul>

View file

@ -0,0 +1,83 @@
---
import {
parseMarkdown,
getQMLTypeLink,
} from "@config/io/helpers";
import type {
QMLTypeLinkObject,
QuickshellProps,
} from "@config/io/types";
import { Tag, Flag } from "@icons";
export interface Props {
propsKeys: string[];
propsData: QuickshellProps;
title: string;
}
const { propsKeys, propsData, title } = Astro.props;
---
<ul class="typedata typeprops">
{
propsKeys.map(item => {
const propData = propsData[item]
let typeLink:string;
let linkText:string;
const gadget = propData.type.gadget;
if (gadget) {
typeLink = "#"
linkText = `[${Object.keys(gadget).toString()}]`
} else {
typeLink = getQMLTypeLink(propData.type as unknown as QMLTypeLinkObject)
linkText = propData.type.name || propData.type.type
}
return (
<li id={ item } class="typedata-root typeprop-root">
<p class="typedata-name typeprop-name">
<Tag client:idle/>
{ item }<span class="type-datatype">:&nbsp;<a
href={typeLink}
target="_blank"
>{ linkText }</a></span>
</p>
{
propData.flags && propData.flags.length > 0 ? (
<p class="type-flags">
{
propData.flags.map((flag) => {
return (
<span class="type-flag">
<Flag client:idle/>
{flag}
</span>
)
})
}
</p>
) : null
}
{
gadget ? (
<p class="typedata-params typefunc-params">
{
Object.keys(gadget).map((key) => {
const gadgetData = gadget[key]
return (
<span class="typedata-param typefunc-param">
<Tag client:idle/>
{key}:<span><a href=`${getQMLTypeLink(gadgetData as unknown as QMLTypeLinkObject)}`>{gadgetData.name}</a></span>
</span>
)
})
}
</p>
):null
}
<section class="typedata-details">
<div id="injectedMd" class="typedata-detailsdata" set:html={parseMarkdown(propData.details, title)} />
</section>
</li>
)
})
}
</ul>

View file

@ -0,0 +1,54 @@
---
import type { QuickshellSignal } from "@config/io/types";
import { Tag, PowerCord } from "@icons";
import { parseMarkdown } from "@config/io/helpers";
export interface Props {
signalKeys: string[];
signalsData: QuickshellSignal;
title: string;
}
const { signalKeys, signalsData, title } = Astro.props;
---
<ul class="typedata typesignals">
{
signalKeys.map(item => {
const signalData = signalsData[item];
const paramKeys = signalData.params.length > 0 ? signalData.params.map((param,index) => `${param.name}${index !== signalData.params.length -1 ? ", ":""}`) : []
return (
<li id={ item } class="typedata-root typesignal-root">
<p class="typedata-name typesignal-name">
<PowerCord client:idle/>
{ item }(<span class="typedata-param">{paramKeys}</span>)<span class="typesignal-doclink"><a
href="/docs/configuration/qml-overview#-signals"
target="_blank"
>?</a></span>
</p>
{
signalData.params && signalData.params.length > 0 ? (
<p class="typesignal-params">
{
signalData.params.map((param, _) => {
return (
<span class="typesignal-param typedata-param">
<Tag client:idle/>
{param.name}<span class="type-datatype">:&nbsp;<a
href=""
target="_blank"
>{param.type.name}</a></span>
</span>
)
})
}
</p>
) : null
}
<section class="typedata-details">
<div class="typedata-detailsdata" set:html={parseMarkdown(signalData.details, title)} />
</section>
</li>
)
})
}
</ul>

View file

@ -0,0 +1,45 @@
---
import type { QuickshellVariant } from "@config/io/types";
import { FourDiamonds } from "../icons";
import { parseMarkdown } from "@src/config/io/helpers";
export interface Props {
variantKeys: string[];
variantsData: QuickshellVariant;
title: string;
}
const { variantKeys, variantsData, title } = Astro.props;
---
<ul class="typedata typevariants">
{
variantKeys.map(item => {
const variantData = variantsData[item];
const paramKeys = variantData.params && variantData.params.length > 0
? variantData.params.map(param => param.name)
: [];
return (
<li id={ item } class="typedata-root typevariant-root">
<p class="typedata-name typevariant-name">
<FourDiamonds client:idle/>
{ item }
</p>
{
paramKeys ? (
<div class="typedata-params typevariant-params">
{paramKeys.map(paramKey => (
<span class="typedata-param typevariant-param">{paramKey}</span>
))}
</div>
)
:null
}
<section class="typedata-details">
<div class="typedata-detailsdata" set:html={parseMarkdown(variantData.details, title)} />
</section>
</li>
)
})
}
</ul>

28
src/config/Head.astro Normal file
View file

@ -0,0 +1,28 @@
---
interface Props {
title: string;
description: string;
// image: string;
}
const { title, description } = Astro.props;
---
<title>{title}</title>
<meta name="description" content={description} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<!-- Open Graph Meta Tags -->
<meta name="og:type" content="website" />
<meta name="og:site_name" content="quickshell" />
<meta name="og:url" content={Astro.url} />
<meta name="og:title" content={title} />
<meta name="og:description" content={description} />
<!-- <meta name="og:image" content={image} /> -->
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:domain" content="quickshell.outfoxxed.me" />
<meta name="twitter:url" content={Astro.url} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<!-- <meta name="twitter:image" content={image} /> -->

23
src/config/PreTheme.astro Normal file
View file

@ -0,0 +1,23 @@
---
---
<script is:inline>
function updateTheme() {
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
// Run on initial load
updateTheme();
// Run on view transitions
document.addEventListener("astro:after-swap", updateTheme);
</script>

View file

@ -0,0 +1,5 @@
export const options = {
includeMatches: true,
minMatchCharLength: 2,
threshold: 0.5,
};

View file

View file

@ -0,0 +1,53 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import type { RouteData, dirData } from "./types";
async function readSubdir(subdir: string): Promise<dirData[]> {
const fullpath = path.join(process.cwd(), "modules", subdir);
const filenames = await fs.readdir(fullpath);
const data = await Promise.all(
filenames.map(async filename => {
const filepath = path.join(fullpath, filename);
const content = await fs.readFile(filepath, "utf8");
const data = JSON.parse(content);
if (typeof data.module === "undefined") {
data.module = "index";
data.contains = filenames
.filter(filename => filename !== "index.json")
.map(filename => filename.replace(".json", ""));
}
const returnValue = {
fullpath: path.join(fullpath, filename),
filename: filename.replace(".json", ""),
category: subdir,
data: data,
};
return returnValue;
})
);
return data;
}
export async function generateTypeData(): Promise<RouteData[]> {
const mainDir = path.join(process.cwd(), "modules");
const subdirs = await fs.readdir(mainDir, {
withFileTypes: true,
});
const routes: RouteData[] = [];
for (const subdir of subdirs) {
const data = await readSubdir(subdir.name);
const returnValue = data.map(entry => {
return {
type: entry.category,
name: entry.filename,
path: entry.fullpath,
data: entry.data,
};
});
routes.push(...returnValue);
}
return routes;
}

238
src/config/io/helpers.ts Normal file
View file

@ -0,0 +1,238 @@
import { marked } from "marked";
import markedAlert from "marked-alert";
import {
// Flag,
PowerCord,
Tag,
FourDiamonds,
RoundBrackets,
} from "@icons";
import type {
ConfigHeading,
ConfigTOC,
GroupedRoutes,
} from "@components/navigation/sidebars/types";
import type { QMLTypeLinkObject, RouteData } from "./types";
export function buildHierarchy(headings: ConfigHeading[]) {
const toc: ConfigTOC[] = [];
const parentHeadings = new Map();
if (!headings || headings.length === 0) {
return toc;
}
for (const h of headings) {
const heading = { ...h, subheadings: [] };
parentHeadings.set(heading.depth, heading);
if (heading.depth === 1) {
toc.push(heading);
} else {
parentHeadings
.get(heading.depth - 1)
.subheadings.push(heading);
}
}
return toc;
}
export function groupRoutes(routes: RouteData[]): GroupedRoutes {
const froutes = routes.filter(route => route.name !== "index");
const defaultValue = {
tutorials: {
configuration: [
{ name: "Intro", type: "intro" },
{ name: "Positioning", type: "positioning" },
{ name: "QML Overview", type: "qml-overview" },
],
},
types: {},
};
return froutes.reduce<GroupedRoutes>((acc, route) => {
if (!acc.tutorials) {
acc.tutorials = {
configuration: [
{ name: "Intro", type: "intro" },
{ name: "Positioning", type: "positioning" },
{ name: "QML Overview", type: "qml-overview" },
],
};
}
if (!acc.types) acc.types = {};
if (!acc.types[route.type]) {
acc.types[route.type] = [];
}
acc.types[route.type].push({
name: route.name,
type: route.type,
});
return acc;
}, defaultValue);
}
export function parseMarkdown(text?: string, title?: string) {
if (!text) {
return marked.parse(`${title}`);
}
return marked.use(markedAlert()).parse(text);
}
export function getQMLTypeLinkObject(unparsed: string) {
const isLocal = unparsed.startsWith("MQS_") ? "local" : false;
const isQT = unparsed.startsWith("MQT_") ? "qt" : false;
const index = isLocal || isQT || "self";
const hashMap = {
local: () => {
const linkSplit = unparsed.slice(4).split("99");
const hasSubmodule = linkSplit[0].indexOf("_") !== -1;
const linkModule = hasSubmodule
? linkSplit[0].replace("_", ".")
: linkSplit[0];
const linkObj: QMLTypeLinkObject = {
type: "local",
module: linkModule.replace("_", "."),
name: linkSplit[1].slice(1),
};
if (linkSplit.length > 2) {
linkObj.mname = linkSplit[2].slice(1);
linkObj.mtype = linkSplit[3].slice(1);
}
return linkObj;
},
qt: () => {
const linkSplit = unparsed.slice(4).split("99");
const hasSubmodule = linkSplit[0].indexOf("_") !== -1;
const linkModule = hasSubmodule
? linkSplit[0].replace("_", "-")
: linkSplit[0];
const linkObj: QMLTypeLinkObject = {
type: "qt",
module: linkModule,
name: linkSplit[1].slice(1),
};
if (linkSplit.length > 2) {
linkObj.mname = linkSplit[3];
linkObj.mtype = linkSplit[4];
}
return linkObj;
},
self: () => {
const linkSplit = unparsed.slice(1).split("99");
const linkObj: QMLTypeLinkObject = {
type: "self",
mname: linkSplit[0],
mtype: linkSplit[1].slice(1),
};
return linkObj;
},
};
return hashMap[index]();
}
export function getQMLTypeLink({
type,
module,
name,
//mtype,
mname,
}: QMLTypeLinkObject) {
if (type === "unknown") {
return "#unknown";
}
const qtStart = "https://doc.qt.io/qt-6/";
const localStart = "/docs/types";
const isSpecific = mname ? `#${mname}` : "";
const hashMap = {
local: () => {
const localLink = `${localStart}/${module}/${name}${isSpecific}`;
return localLink;
},
qt: () => {
const qtLink = `${qtStart}${module!.toLowerCase().replace(".", "-")}-${name!.toLowerCase()}${isSpecific.toLowerCase()}.html`;
return qtLink;
},
self: () => {
const selfLink = `#${mname}`;
return selfLink;
},
};
if (!type) {
type = "self";
}
return hashMap[type as keyof typeof hashMap]();
}
export function getIconForLink(mtype: string, isJsx: boolean) {
const TagIconString: string = `<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class=""
>
<title>Go to</title>
<path
fill="currentColor"
d="M246.66 123.56L201 55.13A15.94 15.94 0 0 0 187.72 48H40a16 16 0 0 0-16 16v128a16 16 0 0 0 16 16h147.72a16 16 0 0 0 13.28-7.12l45.63-68.44a8 8 0 0 0 .03-8.88M187.72 192H40V64h147.72l42.66 64Z"
/>
</svg>`;
const RoundBracketsIconString: string = `<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class=""
>
<title>Go to</title>
<path
fill="currentColor"
d="M40 128c0 58.29 34.67 80.25 36.15 81.16a8 8 0 0 1-8.27 13.7C66.09 221.78 24 195.75 24 128s42.09-93.78 43.88-94.86a8 8 0 0 1 8.26 13.7C74.54 47.83 40 69.82 40 128m148.12-94.86a8 8 0 0 0-8.27 13.7C181.33 47.75 216 69.71 216 128s-34.67 80.25-36.12 81.14a8 8 0 0 0 8.24 13.72C189.91 221.78 232 195.75 232 128s-42.09-93.78-43.88-94.86"
/>
</svg>`;
const PowerCordIconString: string = `<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class=""
>
<title>Go to</title>
<path
fill="currentColor"
d="M149.66 138.34a8 8 0 0 0-11.32 0L120 156.69L99.31 136l18.35-18.34a8 8 0 0 0-11.32-11.32L88 124.69l-18.34-18.35a8 8 0 0 0-11.32 11.32l6.35 6.34l-23.32 23.31a32 32 0 0 0 0 45.26l5.38 5.37l-28.41 28.4a8 8 0 0 0 11.32 11.32l28.4-28.41l5.37 5.38a32 32 0 0 0 45.26 0L132 191.31l6.34 6.35a8 8 0 0 0 11.32-11.32L131.31 168l18.35-18.34a8 8 0 0 0 0-11.32m-52.29 65a16 16 0 0 1-22.62 0l-22.06-22.09a16 16 0 0 1 0-22.62L76 135.31L120.69 180Zm140.29-185a8 8 0 0 0-11.32 0l-28.4 28.41l-5.37-5.38a32.05 32.05 0 0 0-45.26 0L124 64.69l-6.34-6.35a8 8 0 0 0-11.32 11.32l80 80a8 8 0 0 0 11.32-11.32l-6.35-6.34l23.32-23.31a32 32 0 0 0 0-45.26l-5.38-5.37l28.41-28.4a8 8 0 0 0 0-11.32m-34.35 79L180 120.69L135.31 76l23.32-23.31a16 16 0 0 1 22.62 0l22.06 22a16 16 0 0 1 0 22.68Z"
/>
</svg>`;
const FourDiamondsIconString: string = `<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
class=""
>
<title>Go to</title>
<path
fill="currentColor"
d="M122.34 109.66a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32l-40-40a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32ZM128 35.31L156.69 64L128 92.69L99.31 64Zm5.66 111a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32l40 40a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32ZM128 220.69L99.31 192L128 163.31L156.69 192Zm109.66-98.35l-40-40a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32l40 40a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32M192 156.69L163.31 128L192 99.31L220.69 128Zm-82.34-34.35l-40-40a8 8 0 0 0-11.32 0l-40 40a8 8 0 0 0 0 11.32l40 40a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0 0-11.32M64 156.69L35.31 128L64 99.31L92.69 128Z"
/>
</svg>`;
const map = {
prop: () => (isJsx ? Tag : TagIconString),
func: () => (isJsx ? RoundBrackets : RoundBracketsIconString),
signal: () => (isJsx ? PowerCord : PowerCordIconString),
variant: () =>
isJsx ? FourDiamonds : FourDiamondsIconString,
};
return map[mtype as keyof typeof map]();
}

121
src/config/io/types.d.ts vendored Normal file
View file

@ -0,0 +1,121 @@
//#FIXME fuseConfig.ts
// --
// generateSearchLists.ts
interface SearchLists {
slug: string;
link: string;
summary: string;
}
// --
// generateTypeData.ts
interface QuickshellBase {
type: string;
module: string;
name: string;
}
interface QuickshellInstance {
name?: string;
type: {
gadget?: QuickshellGadget;
type: string;
module: string;
name: string;
of?: QuickshellBase;
};
details?: string;
flags?: string[];
}
interface QuickshellGadget {
[key: string]: QuickshellInstance;
}
interface QuickshellProps {
[key: string]: QuickshellInstance;
}
interface QuickshellFunction {
ret: QuickshellInstance;
name: string;
id: string;
details: string;
params: QuickshellInstance[];
}
interface QuickshellSignal {
[key: string]: {
name: string;
details: string;
params: QuickshellInstance[];
};
}
export interface QuickshellVariant {
[key: string]: {
name?: string;
details: string;
params?: QuickshellInstance[];
};
}
export interface QuickshellData {
type: string;
module: string;
name: string;
description: string;
details: string;
flags?: string[];
contains?: string[];
super?: QuickshellBase;
properties?: QuickshellProps;
functions?: QuickshellFunction[];
signals?: QuickshellSignal;
variants?: QuickshellVariant;
subtypes?: QuickshellData[];
}
export interface RouteData {
// priority 1: Quickshell, Quickshell.Io, etc.
type: string;
// priority 1.1: entry name (e.g. DataStreamParser)
name: string;
// path to json
path: string;
// data content of the route
data: QuickshellData;
}
export interface dirData {
fullpath: string;
filename: string;
category: string;
data: QuickshellData;
}
// --
// helpers.ts
interface QMLTypeLinkObject {
type: string;
module?: string;
name?: string;
mtype?: string;
mname?: string;
}
// --
export type {
QuickshellBase,
QuickshellInstance,
QuickshellGadget,
QuickshellProps,
QuickshellFunction,
QuickshellSignal,
QuickshellVariant,
QuickshellData,
RouteData,
dirData,
QMLTypeLinkObject,
};

2
src/env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View file

@ -0,0 +1,29 @@
---
import Header from "@components/Header.astro";
import Head from "@config/Head.astro";
import PreTheme from "@config/PreTheme.astro";
import "@styles/global.css";
interface Props {
title: string;
description: string;
image: string;
}
const { title, description } = Astro.props;
---
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<Head description={description} title={title} />
<link rel="canonical" href={Astro.url} />
<PreTheme />
</head>
<body class="baselayout">
<Header />
<h1>{title}</h1>
<slot />
</body>
</html>

View file

@ -0,0 +1,39 @@
---
import DocsLayout from "@layouts/DocsLayout.astro";
import TOCIntersectionObserver from "@src/components/hooks/TOCIntersectionObserver.astro";
import TOC from "@components/navigation/sidebars/TOC.astro";
export interface Headings {
slug: string;
text: string;
depth: number;
}
export interface Props {
content: {
title: string;
};
headings: Headings[];
frontmatter?: {
title: string;
description: string;
};
}
const { headings, frontmatter } = Astro.props;
---
<DocsLayout
title={frontmatter!.title}
description={frontmatter!.description}
headings={headings}
>
<div class="docs">
<div class="docs-content">
<hr />
<slot />
</div>
</div>
<TOC mobile={false} headings={headings}/>
</DocsLayout>
<TOCIntersectionObserver/>

View file

@ -0,0 +1,103 @@
---
import { Breadcrumbs } from "astro-breadcrumbs";
import "astro-breadcrumbs/breadcrumbs.css";
import CreateCopyButtons from "@components/hooks/CreateCopyButtons.astro";
import PreTheme from "@config/PreTheme.astro";
import Header from "@components/Header.astro";
import Head from "@config/Head.astro";
import Nav from "@components/navigation/sidebars/Nav.astro";
import CreateQMLCodeButtons from "@components/hooks/CreateQMLCodeButtons.astro";
import "@styles/global.css";
import type { ConfigHeading } from "@src/components/navigation/sidebars/types";
interface Props {
title: string;
description: string;
headings?: ConfigHeading[];
}
const { title, description, headings } = Astro.props;
const url = Astro.url.pathname.split("/");
const customBreadcrumbs = [
{
index: 0,
text: "custom",
href: "/",
},
{
text: url[1].slice(0, 1)[0].toUpperCase() + url[1].slice(1),
href: `/${url[1]}`,
},
];
if (url[2]) {
customBreadcrumbs.push({
text: url[2].slice(0, 1)[0].toUpperCase() + url[2].slice(1),
href: `/${url[1]}/${url[2]}`,
});
if (url[3]) {
customBreadcrumbs.push({
text: url[3].slice(0, 1)[0].toUpperCase() + url[3].slice(1),
href: `/${url[1]}/${url[2]}/${url[3]}`,
});
if (url[4]) {
customBreadcrumbs.filter((_, index) => index !== 4);
customBreadcrumbs.push({
text: url[4],
href: `/${url[1]}/${url[2]}/${url[3]}/${url[4]}`,
});
}
}
}
---
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<Head description={description} title={title} />
<link rel="canonical" href={Astro.url} />
<PreTheme />
<CreateCopyButtons />
<CreateQMLCodeButtons/>
</head>
<body class="docslayout">
<Header headings={headings}/>
<div class="docslayout-root">
<Nav mobile={false}/>
<div class="docslayout-inner">
<Breadcrumbs crumbs={customBreadcrumbs} linkTextFormat="sentence" truncated={true}>
<svg
slot="index"
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
>
<title>Home</title>
<path
fill="currentColor"
d="m219.31 108.68l-80-80a16 16 0 0 0-22.62 0l-80 80A15.87 15.87 0 0 0 32 120v96a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-56h32v56a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-96a15.87 15.87 0 0 0-4.69-11.32M208 208h-48v-56a8 8 0 0 0-8-8h-48a8 8 0 0 0-8 8v56H48v-88l80-80l80 80Z"
></path></svg
>
<svg
slot="separator"
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
><path
fill="currentColor"
d="m181.66 133.66l-80 80a8 8 0 0 1-11.32-11.32L164.69 128L90.34 53.66a8 8 0 0 1 11.32-11.32l80 80a8 8 0 0 1 0 11.32"
></path></svg
>
</Breadcrumbs>
<slot />
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,17 @@
---
layout: "@layouts/ConfigLayout.astro"
title: "Configuration"
description: "Configuring the shell"
---
import MD_Title from "@components/MD_Title.tsx"
# <MD_Title titleVar={1}> {frontmatter.title} </MD_Title>
You should start with the [Introduction](./configuration/intro) which will guide you
through the basics of QML by creating a simple topbar with a clock.
From there you can read the [QML Overview](./configuration/qml-overview) to get an overview of
the QML language, or jump right into the [Type Reference](/docs/types) to find
types you can use in your shell.
The [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples) repo contains
fully working example configurations you can read and modify.

View file

@ -0,0 +1,844 @@
---
layout: "@layouts/ConfigLayout.astro"
title: "Introduction"
---
import { DocsCollapsible } from "@components/Collapsible.tsx";
import MD_Title from "@components/MD_Title.tsx"
# <MD_Title titleVar={1}> {frontmatter.title} </MD_Title>
This page will walk you through the process of creating a simple bar/panel, and
introduce you to all the basic concepts involved.
There are many links to the [QML Overview](../qml-overview)
and [Type Reference](/docs/types) which you should follow if you don't
fully understand the concepts involved.
## <MD_Title titleVar={2} client:visible> Shell Files </MD_Title>
Every quickshell instance starts from a shell root file, conventionally named `shell.qml`.
The default path is `~/.config/quickshell/shell.qml`.
(where `~/.config` can be substituted with `$XDG_CONFIG_HOME` if present.)
Each shell file starts with the shell root object. Only one may exist per configuration.
```qml {filename="~/.config/quickshell/shell.qml"}
import Quickshell
QS_Quickshell_ShellRoot {
// ...
}
```
The shell root is not a visual element but instead contains all of the visual
and non visual objects in your shell. You can have multiple different shells
with shared components and different shell roots.
<DocsCollapsible title="Shell search paths and manifests" client:visible>
Quickshell can be launched with configurations in locations other than the default one.
The `-p` or `--path` option will launch the shell root at the given path.
It will also accept folders with a `shell.qml` file in them.
It can also be specified via the `QS_CONFIG_PATH` environment variable.
The `-c` or `--config` option will launch a configuration from the current manifest,
or if no manifest is specified, a subfolder of quickshell's base path.
It can also be specified via the `QS_CONFIG_NAME` environment variable.
The base path defaults to `~/.config/quickshell`, but can be changed using
the `QS_BASE_PATH` environment variable.
The `-m` or `--manifest` option specifies the quickshell manifest to read configs
from. When used with `-c`, the config will be chosen by name from the manifest.
It can also be specified via the `QS_MANIFEST` environment variable.
The manifest path defaults to `~/.config/quickshell/manifest.conf` and is a list
of `name = path` pairs where path can be relative or absolute.
Lines starting with `#` are comments.
```properties
# ~/.config/quickshell/manifest.conf
myconf1 = myconf
myconf2 = ./myconf
myconf3 = myconf/shell.nix
myconf4 = ~/.config/quickshell/myconf
```
You can use `quickshell --current` to print the current values of any of these
options and what set them.
</DocsCollapsible>
## <MD_Title titleVar={2}> Creating Windows </MD_Title>
Quickshell has two main window types available,
[PanelWindow](/docs/types/quickshell/panelwindow) for bars and widgets, and
[FloatingWindow](/docs/types/quickshell/floatingwindow) for standard desktop windows.
We'll start with an example:
```qml
import Quickshell // for ShellRoot and PanelWindow
import QtQuick // for Text
QS_Quickshell_ShellRoot {
QS_Quickshell_PanelWindow {
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
// center the bar in its parent component (the window)
anchors.centerIn: parent
text: "hello world"
}
}
}
```
The above example creates a bar/panel on your currently focused monitor with
a centered piece of [text](https://doc.qt.io/qt-6/qml-qtquick-text.html). It will also reserve space for itself on your monitor.
More information about available properties is available in the [type reference](/docs/types/quickshell/panelwindow).
## <MD_Title titleVar={2}> Running a process </MD_Title>
Now that we have a piece of text, what if it did something useful?
To start with lets make a clock. To get the time we'll use the `date` command.
We can use a [Process](/docs/types/quickshell.io/process) object to run commands
and return their results.
We'll listen to the [DataStreamParser.read](/docs/types/quickshell.io/datastreamparser/#signal.read)
[signal](/docs/configuration/qml-overview/#signals) emitted by
[SplitParser](/docs/types/quickshell.io/splitparser/) using a
[signal handler](/docs/configuration/qml-overview/#signal-handlers)
to update the text on the clock.
> [!note/Note]
> Quickshell live-reloads your code. You can leave it open and edit the
> original file. The panel will reload when you save it.
```qml
import Quickshell
import Quickshell.Io // for Process
import QtQuick
QS_Quickshell_ShellRoot {
QS_Quickshell_PanelWindow {
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
// give the text an ID we can refer to elsewhere in the file
id: clock
anchors.centerIn: parent
// create a process management object
QS_Quickshell00Io_Process {
// the command it will run, every argument is its own string
command: ["date"]
// run the command immediately
running: true
// process the stdout stream using a SplitParser
// which returns chunks of output after a delimiter
stdout: QS_Quickshell00Io_SplitParser {
// listen for the read signal, which returns the data that was read
// from stdout, then write that data to the clock's text property
onRead: data => clock.text = data
}
}
}
}
}
```
## <MD_Title titleVar={2}> Running code at an interval </MD_Title>
With the above example, your bar should now display the time, but it isn't updating!
Let's use a [Timer](https://doc.qt.io/qt-6/qml-qtqml-timer.html) fix that.
```qml
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_ShellRoot {
QS_Quickshell_PanelWindow {
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
id: clock
anchors.centerIn: parent
QS_Quickshell00Io_Process {
// give the process object an id so we can talk
// about it from the timer
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
onRead: data => clock.text = data
}
}
// use a timer to rerun the process at an interval
QT_qtqml_Timer {
// 1000 milliseconds is 1 second
interval: 1000
// start the timer immediately
running: true
// run the timer again when it ends
repeat: true
// when the timer is triggered, set the running property of the
// process to true, which reruns it if stopped.
onTriggered: dateProc.running = true
}
}
}
}
```
## <MD_Title titleVar={2}> Reusable components </MD_Title>
If you have multiple monitors you might have noticed that your bar
is only on one of them. If not, you'll still want to **follow this section
to make sure your bar doesn't disappear if your monitor disconnects**.
We can use a [Variants](/docs/types/quickshell/variants)
object to create instances of _non widget items_.
(See [Repeater](https://doc.qt.io/qt-6/qml-qtquick-repeater.html) for doing
something similar with visual items.)
The `Variants` type creates instances of a
[Component](https://doc.qt.io/qt-6/qml-qtqml-component.html) based on a data model
you supply. (A component is a re-usable tree of objects.)
The most common use of `Variants` in a shell is to create instances of
a window (your bar) based on your monitor list (the data model).
Variants will inject the values in the data model into each new
component's `modelData` property, which means we can easily pass each screen
to its own component.
(See [Window.screen](/docs/types/quickshell/qswindow/#prop.screen).)
```qml
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_ShellRoot {
QS_Quickshell_Variants {
model: Quickshell.screens;
delegate: QT_qtqml_Component {
QS_Quickshell_PanelWindow {
// the screen from the screens list will be injected into this
// property
property var modelData
// we can then set the window's screen to the injected property
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
id: clock
anchors.centerIn: parent
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
onRead: data => clock.text = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
}
}
}
}
```
<span class="small">
See also: [Property
Bindings](/docs/configuration/qml-overview/#property-bindings),
[Variants.component](/docs/types/quickshell/variants/#prop.component),
[Quickshell.screens](/docs/types/quickshell/quickshell/#prop.screens),
[Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
</span>
With this example, bars will be created and destroyed as you plug and unplug them,
due to the reactive nature of the
[Quickshell.screens](/docs/types/quickshell/quickshell/#prop.screens) property.
(See: [Reactive Bindings](/docs/configuration/qml-overview/#reactive-bindings).)
Now there's an important problem you might have noticed: when the window
is created multiple times we also make a new Process and Timer. We can fix
this by moving the Process and Timer outside of the window.
> [!caution/Error]
> This code will not work correctly.
```qml
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_ShellRoot {
QS_Quickshell_Variants {
model: Quickshell.screens
delegate: QT_qtqml_Component {
QS_Quickshell_PanelWindow {
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
id: clock
anchors.centerIn: parent
}
}
}
}
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
onRead: data => clock.text = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
```
However there is a problem with naively moving the Process and Timer
out of the component.
_What about the `clock` that the process references?_
If you run the above example you'll see something like this in the console every second:
```
file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined
file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined
file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined
file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined
file:///home/name/.config/quickshell/shell.qml:33: ReferenceError: clock is not defined
```
This is because the `clock` object, even though it has an ID, cannot be referenced
outside of its component. Remember, components can be created _any number of times_,
including zero, so `clock` may not exist or there may be more than one, meaning
there isn't an object to refer to from here.
We can fix it with a [Property Definition](/docs/configuration/qml-overview/#property-definitions).
We can define a property inside of the ShellRoot and reference it from the clock
text instead. Due to QML's [Reactive Bindings](/docs/configuration/qml-overview/#reactive-bindings),
the clock text will be updated when we update the property for every clock that
currently exists.
```qml
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_ShellRoot {
id: root
// add a property in the root
property string time;
QS_Quickshell_Variants {
model: Quickshell.screens
delegate: QT_qtqml_Component {
QS_Quickshell_PanelWindow {
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
// remove the id as we don't need it anymore
anchors.centerIn: parent
// bind the text to the root's time property
text: root.time
}
}
}
}
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
// update the property instead of the clock directly
onRead: data => root.time = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
```
Now we've fixed the problem so there's nothing actually wrong with the
above code, but we can make it more concise:
1. `Component`s can be defined implicitly, meaning we can remove the
component wrapping the window and place the window directly into the
`delegate` property.
2. The [Variants.delegate](/docs/types/quickshell/variants/#prop.delegate)
property is a [Default Property](/docs/configuration/qml-overview/#the-default-property),
which means we can skip the `delegate:` part of the assignment.
We're already using [ShellRoot](/docs/types/quickshell/shellroot/)'s
default property to store our Variants, Process, and Timer components
among other things.
3. The ShellRoot doesn't actually need an `id` property to talk about
the time property, as it is the outermost object in the file which
has [special scoping rules](/docs/configuration/qml-overview/#property-access-scopes).
This is what our shell looks like with the above (optional) cleanup:
```qml
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_ShellRoot {
property string time;
QS_Quickshell_Variants {
model: Quickshell.screens
QS_Quickshell_PanelWindow {
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
anchors.centerIn: parent
// now just time instead of root.time
text: time
}
}
}
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
// now just time instead of root.time
onRead: data => time = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
```
## <MD_Title titleVar={2}> Multiple files </MD_Title>
In an example as small as this, it isn't a problem, but as the shell
grows it might be preferable to separate it into multiple files.
To start with, let's move the entire bar into a new file.
```qml {filename="shell.qml"}
import Quickshell
QS_Quickshell_ShellRoot {
Bar {}
}
```
```qml {filename="Bar.qml"}
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_Scope {
property string time;
QS_Quickshell_Variants {
model: Quickshell.screens
QS_Quickshell_PanelWindow {
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
QT__Text {
anchors.centerIn: parent
// now just time instead of root.time
text: time
}
}
}
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
// now just time instead of root.time
onRead: data => time = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
```
<span class="small">See also: [Scope](/docs/types/Quickshell/Scope/)</span>
Any qml file that starts with an uppercase letter can be referenced this way.
We can bring in other folders as well using
[import statements](/docs/configuration/qml-overview/#explicit-imports).
Now what about breaking out the clock? This is a bit more complex because
the clock component in the bar, as well as the process and timer that
make up the actual clock, need to be dealt with.
To start with, we can move the clock widget to a new file. For now it's just a
single `Text` object but the same concepts apply regardless of complexity.
```qml {filename="ClockWidget.qml"}
import QtQuick
QT__Text {
// A property the creator of this type is required to set.
// Note that we could just set `text` instead, but don't because your
// clock probably will not be this simple.
required property string time
text: time
}
```
```qml {filename="Bar.qml"}
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_Scope {
id: root
property string time;
QS_Quickshell_Variants {
model: Quickshell.screens
QS_Quickshell_PanelWindow {
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
// the ClockWidget type we just created
ClockWidget {
anchors.centerIn: parent
// Warning: setting `time: time` will bind time to itself which is not what we want
time: root.time
}
}
}
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
onRead: data => time = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
```
While this example is larger than what we had before, we can now expand
on the clock widget without cluttering the bar file.
Let's deal with the clock's update logic now:
```qml {filename="Time.qml"}
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_Scope {
property string time;
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
onRead: data => time = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
```
```qml {filename="Bar.qml"}
import Quickshell
QS_Quickshell_Scope {
// the Time type we just created
Time { id: timeSource }
QS_Quickshell_Variants {
model: Quickshell.screens
QS_Quickshell_PanelWindow {
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
ClockWidget {
anchors.centerIn: parent
// now using the time from timeSource
time: timeSource.time
}
}
}
}
```
## <MD_Title titleVar={2}> Singletons </MD_Title>
Now you might be thinking, why do we need the `Time` type in
our bar file, and the answer is we don't. We can make `Time`
a [Singleton](/docs/configuration/qml-overview/#singletons).
A singleton object has only one instance, and is accessible from
any scope.
```qml {filename="Time.qml"}
// with this line our type becomes a singleton
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
// your singletons should always have Singleton as the type
QS_Quickshell_Singleton {
property string time
QS_Quickshell00Io_Process {
id: dateProc
command: ["date"]
running: true
stdout: QS_Quickshell00Io_SplitParser {
onRead: data => time = data
}
}
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}
```
```qml {filename="ClockWidget.qml"}
import QtQuick
QT__Text {
// we no longer need time as an input
// directly access the time property from the Time singleton
text: Time.time
}
```
```qml {filename="Bar.qml"}
import Quickshell
QS_Quickshell_Scope {
// no more time object
QS_Quickshell_Variants {
model: Quickshell.screens
QS_Quickshell_PanelWindow {
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
height: 30
ClockWidget {
anchors.centerIn: parent
// no more time binding
}
}
}
}
```
## <MD_Title titleVar={2}> JavaScript APIs </MD_Title>
In addition to calling external processes, a [limited set of javascript interfaces] is available.
We can use this to improve our clock by using the [Date API] instead of calling `date`.
[limited set of javascript interfaces]: https://doc.qt.io/qt-6/qtqml-javascript-functionlist.html
[Date API]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
```qml {filename="Time.qml"}
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
QS_Quickshell_Singleton {
property var date: new Date()
property string time: date.toLocaleString(Qt.locale())
QT_qtqml_Timer {
interval: 1000
running: true
repeat: true
onTriggered: date = new Date()
}
}
```

View file

@ -0,0 +1,117 @@
---
layout: "@layouts/ConfigLayout.astro"
title: "Positioning"
---
import MD_Title from "@components/MD_Title.tsx"
# <MD_Title titleVar={1}> {frontmatter.title} </MD_Title>
QtQuick has multiple ways to position components. This page has instructions for where and how
to use them.
## <MD_Title titleVar={2}> Anchors </MD_Title>
Anchors can be used to position components relative to another neighboring component.
It is faster than [manual positioning](#manual-positioning) and covers a lot of simple
use cases.
The [Qt Documentation: Positioning with Anchors](https://doc.qt.io/qt-6/qtquick-positioning-anchors.html)
page has comprehensive documentation of anchors.
## <MD_Title titleVar={2}> Layouts </MD_Title>
Layouts are useful when you have many components that need to be positioned relative to
eachother such as a list.
The [Qt Documentation: Layouts Overview](https://doc.qt.io/qt-6/qtquicklayouts-overview.html)
page has good documentation of the basic layout types and how to use them.
> [!note/Note:]
> Layouts by default have a nonzero spacing.
## <MD_Title titleVar={2}> Manual Positioning </MD_Title>
If layouts and anchors can't easily fulfill your usecase, you can also manually position and size
components by setting their `x`, `y`, `width` and `height` properties, which are relative to
the parent component.
This example puts a 100x100px blue rectangle at x=20,y=40 in the parent item. Ensure the size
of the parent is large enough for its content or positioning based on them will break.
```qml
QT__Item {
// make sure the component is large enough to fit its children
implicitWidth: childrenRect.width
implicitHeight: childrenRect.height
QT__Rectangle {
color: "blue"
x: 20
y: 40
width: 100
height: 100
}
}
```
## <MD_Title titleVar={2}> Notes </MD_Title>
### <MD_Title titleVar={3}> Component Size </MD_Title>
The [Item.implicitHeight] and [Item.implicitWidth] properties control the _base size_ of a
component, before layouts are applied. These properties are _not_ the same as
[Item.height] and [Item.width] which are the final size of the component.
You should nearly always use the implicit size properties when creating a component,
however using the normal width and height properties is fine if you know an
item will never go in a layout.
[Item.height]: https://doc.qt.io/qt-6/qml-qtquick-item.html#height-prop
[Item.width]: https://doc.qt.io/qt-6/qml-qtquick-item.html#width-prop
[Item.implicitHeight]: https://doc.qt.io/qt-6/qml-qtquick-item.html#implicitHeight-prop
[Item.implicitWidth]: https://doc.qt.io/qt-6/qml-qtquick-item.html#implicitWidth-prop
This example component puts a colored rectangle behind some text, and will act the same
way in a layout as the text by itself.
```qml {filename="TextWithBkgColor.qml"}
QT__Rectangle {
implicitWidth: text.implicitWidth
implicitHeight: text.implicitHeight
QT__Text {
id: text
text: "hello!"
}
}
```
If you want to size your component based on multiple others or use any other math you can.
```qml {filename="PaddedTexts.qml"}
QT__Item {
// width of both texts plus 5
implicitWidth: text1.implicitWidth + text2.implicitWidth + 5
// max height of both texts plus 5
implicitHeight: Math.min(text1.implicitHeight, text2.implicitHeight) + 5
QT__Text {
id: text1
text: "text1"
}
QT__Text {
id: text2
anchors.left: text1.left
text: "text2"
}
}
```
### <MD_Title titleVar={3}> Coordinate space </MD_Title>
You should always position or size components relative to the closest possible
parent. Often this is just the `parent` property.
Refrain from using things like the size of your screen to size a component,
as this will break as soon as anything up the component hierarchy changes, such
as adding padding to a bar.

View file

@ -0,0 +1,888 @@
---
layout: "@layouts/ConfigLayout.astro"
title: "QML Overview"
---
import MD_Title from "@components/MD_Title.tsx"
import { DocsCollapsible } from "@components/Collapsible.tsx";
# <MD_Title titleVar={1}> {frontmatter.title} </MD_Title>
Quickshell is configured using the Qt Modeling Language, or QML.
This page explains what you need to know about QML to start using quickshell.
<span class="small">
See also: [Qt Documentation: QML
Tutorial](https://doc.qt.io/qt-6/qml-tutorial.html)
</span>
## <MD_Title titleVar={2}> Structure </MD_Title>
Below is a QML document showing most of the syntax.
Keep it in mind as you read the detailed descriptions below.
> [!note/Notes:]
>
> - Semicolons are permitted basically everywhere, and recommended in
> functions and expressions.
> - While types can often be elided, we recommend you use them where
> possible to catch problems early instead of running into them unexpectedly later on.
```qml
// QML Import statement
import QtQuick 6.0
// Javascript import statement
import "myjs.js" as MyJs
// Root Object
QT__Item {
// Id assignment
id: root
// Property declaration
property int myProp: 5;
// Property binding
width: 100
// Property binding
height: width
// Multiline property binding
prop: {
// ...
5
}
// Object assigned to a property
objProp: Object {
// ...
}
// Object assigned to the parent's default property
AnotherObject {
// ...
}
// Signal declaration
signal foo(bar: int)
// Signal handler
onSignal: console.log("received signal!")
// Property change signal handler
onWidthChanged: console.log(`width is now ${width}!`)
// Multiline signal handler
onOtherSignal: {
console.log("received other signal!");
console.log(`5 * 2 is ${dub(5)}`);
// ...
}
// Attached property signal handler
Component.onCompleted: MyJs.myfunction()
// Function
function dub(x: int): int {
return x * 2
}
}
```
### <MD_Title titleVar={3}> Imports </MD_Title>
#### <MD_Title titleVar={4}> Manual imports </MD_Title>
Every QML File begins with a list of imports.
Import statements tell the QML engine where
to look for types you can create [objects](#objects) from.
A module import statement looks like this:
```qml
import <Module> [Major.Minor] [as <Namespace>]
```
- `Module` is the name of the module you want to import, such as `QtQuick`.
- `Major.Minor` is the version of the module you want to import.
- `Namespace` is an optional namespace to import types from the module under.
A subfolder import statement looks like this:
```qml
import "<directory>" [as <Namespace>]
```
- `directory` is the directory to import, relative to the current file.
- `Namespace` is an optional namespace to import types from the folder under.
A javascript import statement looks like this:
```qml
import "<filename>" as <Namespace>
```
- `filename` is the name of the javascript file to import.
- `Namespace` is the namespace functions and variables from the javascript
file will be made available under.
Note: All _Module_ and _Namespace_ names must start with an uppercase letter.
Attempting to use a lowercase namespace is an error.
##### <MD_Title titleVar={5}> Examples </MD_Title>
```qml
import QtQuick
import QtQuick.Controls 6.0
import Quickshell as QS
import QtQuick.Layouts 6.0 as L
import "jsfile.js" as JsFile
```
<DocsCollapsible title="When no module version" client:visible>
By default, when no module version is requested, the QML engine will pick
the latest available version of the module. Requesting a specific version
can help ensure you get a specific version of the module's types, and as a
result your code doesn't break across Qt or quickshell updates.
While Qt's types usually don't majorly change across versions, quickshell's
are much more likely to break. To put off dealing with the breakage we suggest
specifying a version at least when importing quickshell modules.
</DocsCollapsible>
<span class="small">
[Qt Documentation: Import
syntax](https://doc.qt.io/qt-6/qtqml-syntax-imports.html)
</span>
#### <MD_Title titleVar={4}> Implicit imports </MD_Title>
The QML engine will automatically import any [types](#creating-types) in neighboring files
with names that start with an uppercase letter.
```
root
|-MyButton.qml
|-shell.qml
```
In this example, `MyButton` will automatically be imported as a type usable from shell.qml
or any other neighboring files.
### <MD_Title titleVar={3}> Objects </MD_Title>
Objects are instances of a type from an imported module.
The name of an object must start with an uppercase letter.
This will always distinguish an object from a property.
An object looks like this:
```qml
Name {
id: foo
// properties, functions, signals, etc...
}
```
Every object can contain [properties](#properties), [functions](#functions),
and [signals](#signals). You can find out what properties are available for a type
by looking it up in the [Type Reference](/docs/types/).
#### <MD_Title titleVar={4}> Properties </MD_Title>
Every object may have any number of property assignments (only one per specific property).
Each assignment binds the named property to the given expression.
##### <MD_Title titleVar={5}> Property bindings </MD_Title>
Expressions are snippets of javascript code assigned to a property. The last (or only) line
can be the return value, or an explicit return statement (multiline expressions only) can be used.
```qml
QT__Item {
// simple expression
property: 5
// complex expression
property: 5 * 20 + this.otherProperty
// multiline expression
property: {
const foo = 5;
const bar = 10;
foo * bar
}
// multiline expression with return
property: {
// ...
return 5;
}
}
```
Semicolons are optional and allowed on any line of a single or multiline expression,
including the last line.
All property bindings are [_reactive_](#reactive-bindings), which means when any property the expression depends
on is updated, the expression is re-evaluated and the property is updated.
<span class="small">See: [Reactive bindings](#reactive-bindings)</span>
Note that it is an error to try to assign to a property that does not exist.
(See: [property definitions](#property-definitions))
##### <MD_Title titleVar={5}> Property definitions </MD_Title>
Properties can be defined inside of objects with the following syntax:
```qml
[required] [readonly] [default] property <type> <name>[: binding]
```
- `required` forces users of this type to assign this property. See [Creating Types](#creating-types) for details.
- `readonly` makes the property not assignable. Its binding will still be [reactive](#reactive-bindings).
- `default` makes the property the [default property](#the-default-property) of this type.
- `type` is the type of the property. You can use `var` if you don't know or don't care but be aware that `var` will
allow any value type.
- `name` is the name that the property is known as. It cannot start with an uppercase letter.
- `binding` is the property binding. See [Property bindings](#property-bindings) for details.
```qml
QT__Item {
// normal property
property int foo: 3
// readonly property
readonly property string bar: "hi!"
// bound property
property var things: [ "foo", "bar" ]
}
```
Defining a property with the same name as one provided by the current object will override
the property of the type it is derived from in the current context.
##### <MD_Title titleVar={5}> The default property </MD_Title>
Types can have a _default property_ which must accept either an object or a list of objects.
The default property will allow you to assign a value to it without using the name of the property:
```qml
QT__Item {
// normal property
foo: 3
// this item is assigned to the outer object's default property
QT__Item {
}
}
```
If the default property is a list, you can put multiple objects into it the same way as you
would put a single object in:
```qml
QT__Item {
// normal property
foo: 3
// this item is assigned to the outer object's default property
QT__Item {
}
// this one is too
QT__Item {
}
}
```
##### <MD_Title titleVar={5}> The `id` property </MD_Title>
Every object has a special property called `id` that can be assigned to give
the object a name it can be referred to throughout the current file. The id must be lowercase.
```qml
QT_qtquick11layouts_ColumnLayout {
QT__Text {
id: text
text: "Hello World!"
}
QT_qtquick11controls_Button {
text: "Make the text red";
onClicked: text.color = "red";
}
}
```
<DocsCollapsible title="The `id` property compared to normal properties" client:visible>
The `id` property isn't really a property, and doesn't do anything other than
expose the object to the current file. It is only called a property because it
uses very similar syntax to one, and is the only exception to standard property
definition rules. The name `id` is always reserved for the id property.
</DocsCollapsible>
##### <MD_Title titleVar={5}> Property access scopes </MD_Title>
Properties are "in scope" and usable in two cases.
1. They are defined for current type.
2. They are defined for the root type in the current file.
You can access the properties of any object by setting its [id property](#the-id-property),
or make sure the property you are accessing is from the current object using `this`.
The `parent` property is also defined for all objects, but may not always point to what it
looks like it should. Use the `id` property if `parent` does not do what you want.
```qml
QT__Item {
property string rootDefinition
QT__Item {
id: mid
property string midDefinition
QT__Text {
property string innerDefinition
// legal - innerDefinition is defined on the current object
text: innerDefinition
// legal - innerDefinition is accessed via `this` to refer to the current object
text: this.innerDefinition
// legal - width is defined for Text
text: width
// legal - rootDefinition is defined on the root object
text: rootDefinition
// illegal - midDefinition is not defined on the root or current object
text: midDefinition
// legal - midDefinition is accessed via `mid`'s id.
text: mid.midDefinition
// legal - midDefinition is accessed via `parent`
text: parent.midDefinition
}
}
}
```
<span class="small">
[Qt Documentation: Scope and Naming
Resolution](https://doc.qt.io/qt-6/qtqml-documents-scope.html)
</span>
#### <MD_Title titleVar={4}> Functions </MD_Title>
Functions in QML can be declared everywhere [properties](#properties) can, and follow
the same [scoping rules](#property-access-scopes).
Function definition syntax:
```qml
function <name>(<paramname>[: <type>][, ...])[: returntype] {
// multiline expression (note that `return` is required)
}
```
Functions can be invoked in expressions. Expression reactivity carries through
functions, meaning if one of the properties a function depends on is re-evaluated,
every expression depending on the function is also re-evaluated.
```qml
QT_qtquick11layouts_ColumnLayout {
property int clicks: 0
function makeClicksLabel(): string {
return "the button has been clicked " + clicks + " times!";
}
QT_qtquick11controls_Button {
text: "click me"
onClicked: clicks += 1
}
QT__Text {
text: makeClicksLabel()
}
}
```
In this example, every time the button is clicked, the label's count increases
by one, as `clicks` is changed, which triggers a re-evaluation of `text` through
`makeClicksLabel`.
##### <MD_Title titleVar={5}> Lambdas </MD_Title>
Functions can also be values, and you can assign them to properties or pass them to
other functions (callbacks). There is a shorter way to write these functions, known
as lambdas.
Lambda syntax:
```qml
<params> => <expression>
// params can take the following forms:
() => ... // 0 parameters
<name> => ... // 1 parameter
(<name>[, ...]) => ... // 1+ parameters
// the expression can be either a single or multiline expression.
... => <result>
... => {
return <result>;
}
```
Assigning functions to properties:
```qml
QT__Item {
// using functions
function dub(number: int): int { return number * 2; }
property var operation: dub
// using lambdas
property var operation: number => number * 2
}
```
An overcomplicated click counter using a lambda callback:
```qml
QT_qtquick11layouts_ColumnLayout {
property int clicks: 0
function incrementAndCall(callback) {
clicks += 1;
callback(clicks);
}
QT_qtquick11controls_Button {
text: "click me"
onClicked: incrementAndCall(clicks => {
label.text = `the button was clicked ${clicks} time(s)!`;
})
}
QT__Text {
id: label
text: "the button has not been clicked"
}
}
```
#### <MD_Title titleVar={4}> Signals </MD_Title>
A signal is basically an event emitter you can connect to and receive updates from.
They can be declared everywhere [properties](#properties) and [functions](#functions)
can, and follow the same [scoping rules](#property-access-scopes).
<span class="small">
[Qt Documentation: Signal and Handler Event
System](https://doc.qt.io/qt-6/qtqml-syntax-signals.html)
</span>
##### <MD_Title titleVar={5}> Signal definitions </MD_Title>
A signal can be explicitly defined with the following syntax:
```qml
signal <name>(<paramname>: <type>[, ...])
```
##### <MD_Title titleVar={5}> Making connections </MD_Title>
Signals all have a `connect(<function>)` method which invokes the given function
or signal when the signal is emitted.
```qml
QT_qtquick11layouts_ColumnLayout {
property int clicks: 0
function updateText() {
clicks += 1;
label.text = `the button has been clicked ${clicks} times!`;
}
QT_qtquick11controls_Button {
id: button
text: "click me"
}
QT__Text {
id: label
text: "the button has not been clicked"
}
Component.onCompleted: {
button.clicked.connect(updateText)
}
}
```
<span class="small">
`Component.onCompleted` will be addressed later in [Attached
Properties](#attached-properties) but for now just know that it runs
immediately once the object is fully initialized.
</span>
When the button is clicked, the button emits the `clicked` signal which we connected to
`updateText`. The signal then invokes `updateText` which updates the counter and the
text on the label.
##### <MD_Title titleVar={5}> Signal handlers </MD_Title>
Signal handlers are a more concise way to make a connections, and prior examples have used them.
When creating an object, for every signal present on its type there is a corresponding `on<Signal>`
property implicitly defined which can be set to a function. (Note that the first letter of the
signal's name it capitalized.)
Below is the same example as in [Making Connections](#making-connections),
this time using the implicit signal handler property to handle `button.clicked`.
```qml
QT_qtquick11layouts_ColumnLayout {
property int clicks: 0
function updateText() {
clicks += 1;
label.text = `the button has been clicked ${clicks} times!`;
}
QT_qtquick11controls_Button {
text: "click me"
onClicked: updateText()
}
QT__Text {
id: label
text: "the button has not been clicked"
}
}
```
##### <MD_Title titleVar={5}> Indirect signal handlers </MD_Title>
When it is not possible or not convenient to directly define a signal handler, before resorting
to `.connect`ing the properties, a [Connections] object can be used to access them.
This is especially useful to connect to signals of singletons.
```qml
QT__Item {
QT_qtquick11controls_Button {
id: myButton
text "click me"
}
QT_qtqml_Connections {
target: myButton
function onClicked() {
// ...
}
}
}
```
##### <MD_Title titleVar={5}> Property change signals </MD_Title>
Every property has an associated signal, which powers QML's [reactive bindings](#reactive-bindings).
The signal is named `<propertyname>Changed` and works exactly the same as any other signal.
Whenever the property is re-evaluated, its change signal is emitted. This is used internally
to update dependent properties, but can be directly used, usually with a signal handler.
```qml
QT_qtquick11layouts_ColumnLayout {
CheckBox {
text: "check me"
onCheckStateChanged: {
label.text = labelText(checkState == Qt.Checked);
}
}
QT__Text {
id: label
text: labelText(false)
}
function labelText(checked): string {
return `the checkbox is checked: ${checked}`;
}
}
```
In this example we listen for the `checkState` property of the CheckBox changing
using its change signal, `checkStateChanged` with the signal handler `onCheckStateChanged`.
Since text is also a property we can do the same thing more concisely:
```qml
QT_qtquick11layouts_ColumnLayout {
CheckBox {
id: checkbox
text: "check me"
}
QT__Text {
id: label
text: labelText(checkbox.checkState == Qt.Checked)
}
function labelText(checked): string {
return `the checkbox is checked: ${checked}`;
}
}
```
And the function can also be inlined to an expression:
```qml
QT_qtquick11layouts_ColumnLayout {
CheckBox {
id: checkbox
text: "check me"
}
QT__Text {
id: label
text: {
const checked = checkbox.checkState == Qt.Checked;
return `the checkbox is checked: ${checked}`;
}
}
}
```
You can also remove the return statement if you wish.
##### <MD_Title titleVar={5}> Attached objects </MD_Title>
Attached objects are additional objects that can be associated with an object
as decided by internal library code. The documentation for a type will
tell you if it can be used as an attached object and how.
Attached objects are accessed in the form `<Typename>.<member>` and can have
properties, functions and signals.
A good example is the [Component](https://doc.qt.io/qt-6/qml-qtqml-component.html) type,
which is attached to every object and often used to run code when an object initializes.
```qml
QT__Text {
Component.onCompleted: {
text = "hello!"
}
}
```
In this example, the text property is set inside the `Component.onCompleted` attached signal handler.
#### <MD_Title titleVar={4}> Creating types </MD_Title>
Every QML file with an uppercase name is implicitly a type, and can be used from
neighboring files or imported (See [Imports](#imports).)
A type definition is just a normal object. All properties defined for the root object
are visible to the consumer of the type. Objects identified by [id properties](#the-id-property)
are not visible outside the file.
```qml
// MyText.qml
QT__Rectangle {
required property string text
color: "red"
implicitWidth: textObj.implicitWidth
implicitHeight: textObj.implicitHeight
QT__Text {
id: textObj
anchors.fill: parent
text: parent.text
}
}
// AnotherComponent.qml
QT__Item {
MyText {
// The `text` property of `MyText` is required, so we must set it.
text: "Hello World!"
// `anchors` is a property of `Item` which `Rectangle` subclasses,
// so it is available on MyText.
anchors.centerIn: parent
// `color` is a property of `Rectangle`. Even though MyText sets it
// to "red", we can override it here.
color: "blue"
// `textObj` is has an `id` within MyText.qml but is not a property
// so we cannot access it.
textObj.color: "red" // illegal
}
}
```
##### <MD_Title titleVar={5}> Singletons </MD_Title>
QML Types can be easily made into a singleton, meaning there is only one instance
of the type.
To make a type a singleton, put `pragma Singleton` at the top of the file.
To ensure it behaves correctly with quickshell you should also make
[Singleton](/docs/types/quickshell/singleton) the root item of your type.
```qml
pragma Singleton
import ...
QS_Quickshell_Singleton {
...
}
```
once a type is a singleton, its members can be accessed by name from neighboring
files.
## <MD_Title titleVar={2}> Concepts </MD_Title>
### <MD_Title titleVar={3}> Reactive bindings </MD_Title>
<span class="small">
This section assumes knowledge of: [Properties](#properties),
[Signals](#signals), and [Functions](#functions). See also the [Qt
documentation](https://doc.qt.io/qt-6/qtqml-syntax-propertybinding.html).
</span>
Reactivity is when a property is updated based on updates to another property.
Every time one of the properties in a binding change, the binding is re-evaluated
and the bound property takes the new result. Any bindings that depend on that property
are then re-evaluated and so forth.
Bindings can be created in two different ways:
##### <MD_Title titleVar={5}> Automatic bindings </MD_Title>
A reactive binding occurs automatically when you use one or more properties in the definition
of another property. .
```qml
QT__Item {
property int clicks: 0
QT_qtquick11controls_Button {
text: `clicks: ${clicks}`
onClicked: clicks += 1
}
}
```
In this example, the button's `text` property is re-evaluated every time the button is clicked, because
the `clicks` property has changed.
###### <MD_Title titleVar={6}> Avoiding creation </MD_Title>
To avoid creating a binding, do not use any other properties in the definition of a property.
You can use the `Component.onCompleted` signal to set a value using a property without creating a binding,
as assignments to properties do not create binding.
```qml
QT__Item {
property string theProperty: "initial value"
QT__Text {
// text: "Right now, theProperty is: " + theProperty
Component.onCompleted: text = "At creation time, theProperty is: " + theProperty
}
}
```
##### <MD_Title titleVar={5}> Manual bindings </MD_Title>
Sometimes (not often) you need to create a binding inside of a function, signal, or expression.
If you need to change or attach a binding at runtime, the `Qt.binding` function can be used to
create one.
The `Qt.binding` function takes another function as an argument, and when assigned to a property,
the property will use that function as its binding expression.
```qml
QT__Item {
QT__Text {
id: boundText
text: "not bound to anything"
}
QT_qtquick11controls_Button {
text: "bind the above text"
onClicked: {
if (boundText.text == "not bound to anything") {
text = "press me";
boundText.text = Qt.binding(() => `button is pressed: ${this.pressed}`);
}
}
}
}
```
In this example, `boundText`'s `text` property is bound to the button's pressed state
when the button is first clicked. When you press or unpress the button the text will
be updated.
##### <MD_Title titleVar={5}> Removing bindings </MD_Title>
To remove a binding, just assign a new value to the property without using `Qt.binding`.
```qml
QT__Item {
QT__Text {
id: boundText
text: `button is pressed: ${theButton.pressed}`
}
QT_qtquick11controls_Button {
id: theButton
text: "break the binding"
onClicked: boundText.text = `button was pressed at the time the binding was broken: ${pressed}`
}
}
```
When the button is first pressed, the text will be updated, but once `onClicked` fires
the text will be unbound, and even though it contains a reference to the `pressed` property,
it will not be updated further by the binding.
### <MD_Title titleVar={3}> Lazy loading </MD_Title>
Often not all of your interface needs to load immediately. By default the QML
engine initializes every object in the scene before showing anything onscreen.
For parts of the interface you don't need to be immediately visible, load them
asynchronously using a [LazyLoader](/docs/types/quickshell/lazyloader).
See its documentation for more information.
#### <MD_Title titleVar={4}> Components </MD_Title>
Another delayed loading mechanism is the [Component](https://doc.qt.io/qt-6/qml-qtqml-component.html) type.
This type can be used to create multiple instances of objects or lazily load them. It's used by types such
as [Repeater](https://doc.qt.io/qt-6/qml-qtquick-repeater.html)
and [Quickshell.Variants](/docs/types/quickshell/variants)
to create instances of a component at runtime.

View file

@ -0,0 +1,10 @@
---
import DocsLayout from "@layouts/DocsLayout.astro";
---
<DocsLayout title="Quickshell Docs" description="Quickshell Documentation">
<h2>Docs</h2>
<div class="root-nav">
<h3><a href="/docs/configuration">Configuration</a></h3>
<h3><a href="/docs/types">Type Definitions</a></h3>
</div>
</DocsLayout>

View file

@ -0,0 +1,127 @@
---
import {
parseMarkdown,
getQMLTypeLink,
} from "@config/io/helpers";
import { generateTypeData } from "@config/io/generateTypeData";
import { Flag } from "@icons";
import DocsLayout from "@layouts/DocsLayout.astro";
import TOC from "@components/navigation/sidebars/TOC.astro";
import Properties from "@components/type/Properties.astro";
import Functions from "@components/type/Functions.astro";
import Signals from "@components/type/Signals.astro";
import Variants from "@components/type/Variants.astro";
import TransformMDCodeblocks from "@components/hooks/TransformMDCodeblocks.astro";
import TransformLinks from "@components/hooks/TransformLinks.astro";
export async function getStaticPaths() {
const routes = await generateTypeData();
return routes
.filter(route => route.name !== "index")
.map(route => ({
params: { type: route.type, name: route.name },
props: { route },
}));
}
const { route } = Astro.props;
const data = route.data;
const sidebarFunctions =
data.functions?.map(item => item.name) || null;
const propsKeys = data.properties
? Object.keys(data.properties)
: null;
const signalKeys = data.signals
? Object.keys(data.signals)
: null;
const variantKeys = data.variants
? Object.keys(data.variants)
: null;
const sidebarData = {
properties: propsKeys,
functions: sidebarFunctions,
signals: signalKeys,
variants: variantKeys,
};
const superLink = data.super ? getQMLTypeLink(data.super) : null;
const details = parseMarkdown(data.details, route.name);
---
<TransformLinks/>
<TransformMDCodeblocks/>
<DocsLayout title={`${route.name} - ${route.type}`} description={data?.description ?? ""}>
<div class="docs">
<div class="typedocs-content">
<hr />
<h2 class="typedocs-title">
{route.name}:
{data.super ? (
<a
target="_blank"
href={superLink!}
>
{data.super.name}
</a>
):(
<span class="type-datatype">{data.type}</span>
)
}
</h2>
{
route && data ? (
<section class="typedocs-data typedata">
<subheading class="typedocs-subheading">
<code class="type-module">import {data.module}</code>
{data.flags ? (
<div class="type-flags">{data.flags.map(flag => (
<span class="type-flag">
<Flag client:idle/>
{flag}
</span>
))}</div>
):null}
<span id="injectedMd" set:html={details}/>
{!details ? (<span class="toparse">{data.description}</span>):null}
</subheading>
{ data.properties && propsKeys ? (
<Properties
propsData={data.properties}
propsKeys={propsKeys!}
title={route.name}
/>
): null}
{ data.functions && data.functions.length > 0 ? (
<Functions
funcData={data.functions}
title={route.name}
/>
): null}
{ data.signals && signalKeys ? (
<Signals
signalsData={data.signals}
signalKeys={signalKeys}
title={route.name}
/>
):null}
{ data.variants && variantKeys ? (
<Variants
variantsData={data.variants}
variantKeys={variantKeys}
title={route.name}
/>
):null}
</section>
) : null
}
</div>
<TOC mobile={false} types={sidebarData}/>
</div>
</DocsLayout>

View file

@ -0,0 +1,27 @@
---
import DocsLayout from "@layouts/DocsLayout.astro";
import { generateTypeData } from "@config/io/generateTypeData";
export async function getStaticPaths() {
const routes = await generateTypeData();
return routes.map(route => ({
params: { type: route.type, name: route.type },
props: { route },
}));
}
const { route } = Astro.props;
---
<DocsLayout
title={route.type + " Type Documentation"}
description="Quickshell Type Documentation"
>
<hr />
<h2>{route.type[0].toUpperCase() + route.type.slice(1)} Definitions</h2>
<div class="root-nav">
{route.data.contains!.map((item:string) => {
return (<div><a class="root-nav-entry" href={`/docs/types/${route.data.module === "index"? route.data.name : route.data.module}/${item}`}>{item}</a></div>)
})}
</div>
</DocsLayout>

View file

@ -0,0 +1,8 @@
---
import DocsLayout from "@layouts/DocsLayout.astro";
---
<DocsLayout title="Quickshell Type Definitions" description="Quickshell Type Documentation">
<h2>Type Definitions</h2>
<div class="root-nav">
</div>
</DocsLayout>

13
src/pages/index.astro Normal file
View file

@ -0,0 +1,13 @@
---
import BaseLayout from "@layouts/BaseLayout.astro";
---
<BaseLayout title="Quickshell" description="A fully user customizable desktop shell" image="/quickshell.png">
<h2>A fully user customizable desktop shell</h2>
<h3>Based on QtQuick</h3>
<div class="root-nav">
<h3><a href="/docs/configuration">Configuration</a></h3>
<h3><a href="/docs/types">Type Definitions</a></h3>
<h3><a href="https://git.outfoxxed.me/outfoxxed/quickshell-examples">Examples</a></h3>
<h3><a href="https://git.outfoxxed.me/outfoxxed/quickshell">Source</a></h3>
</div>
</BaseLayout>

View file

@ -0,0 +1,107 @@
@property --percent {
syntax: '<percentage>';
initial-value: 0%;
inherits: false;
}
@keyframes pulseGreen {
0% {
background-color: hsl(var(--blue) 85 35 / 0.1);
color: hsl(var(--blue) 100 69 / 1.0);
}
50% {
background-color: hsl(var(--green) 85 35 / 0.5);
color: hsl(var(--green) 100 69 / 1.0);
}
100% {
background-color: hsl(var(--blue) 85 35 / 0.1);
color: hsl(var(--blue) 100 69 / 1.0);
}
}
@keyframes slideDown {
from {
opacity: 0.001;
height: 0;
}
to {
opacity: 1;
height: var(--height);
}
}
@keyframes slideUp {
from {
opacity: 1;
height: var(--height);
}
to {
opacity: 0;
height: 0;
}
}
@keyframes rotateIn {
from {
transform: rotate(0deg);
}
to {
transform: rotate(180deg);
}
}
@keyframes rotateOut {
from {
transform: rotate(180deg);
}
to {
transform: rotate(0deg);
}
}
@keyframes rotateIn90 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(90deg);
}
}
@keyframes rotateOut90 {
from {
transform: rotate(90deg);
}
to {
transform: rotate(0deg);
}
}
@keyframes percentToFifty {
from {
--percent: 0%;
}
to {
--percent: 50%;
}
}
@keyframes percentToZero {
from {
--percent: 50%;
}
to {
--percent: 0%;
}
}

View file

@ -0,0 +1,79 @@
html {
font-family: 'Rubik', Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.272;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color-scheme: light dark;
/* accent */
--green: 141;
--accent-400: var(--green) 100 67;
--accent-500: var(--green) 100 57;
--accent-600: var(--green) 98 50;
/* secondary */
--blue: 224;
--secondary-400: var(--blue) 100 68;
--secondary-500: var(--blue) 100 58;
--secondary-600: var(--blue) 53 41;
--secondary-700: var(--blue) 43 31;
--secondary-800: var(--blue) 23 21;
--secondary-900: var(--blue) 44 7;
/* primary */
--white: 194;
--bg-400: var(--white) 33 100;
--bg-500: var(--white) 12 96;
--bg-600: var(--white) 12 76;
--bg-700: var(--white) 12 56;
--bg-800: var(--white) 12 36;
--bg-900: var(--white) 12 16;
}
html.dark {
/* accent */
--green: 141;
--accent-400: var(--green) 100 67;
--accent-500: var(--green) 95 55;
--accent-600: var(--green) 90 40;
/* secondary */
--white: 194;
--secondary-400: var(--white) 33 100;
--secondary-500: var(--white) 33 96;
--secondary-600: var(--white) 33 76;
--secondary-700: var(--white) 33 56;
--secondary-800: var(--white) 35 36;
--secondary-900: var(--white) 44 7;
/* primary */
--blue: 224;
--bg-400: var(--blue) 90 65;
--bg-500: var(--blue) 83 45;
--bg-700: var(--blue) 82 25;
--bg-800: var(--blue) 82 15;
--bg-900: var(--blue) 82 3;
/* docs */
--prop-color: 350 78 70;
--prop-link-color: 350 78 60;
--func-color: 50 78 70;
--func-link-color: 50 78 60;
--signal-color: 270 78 70;
--signal-link-color: 270 85 60;
--var-color: 190 78 70;
--var-link-color: 190 85 60;
--inner-param-color: 215 60 70;
}
* {
box-sizing: border-box;
position: relative;
margin: 0;
padding: 0;
}

View file

@ -0,0 +1,42 @@
:where(p, li):has(>code) code {
padding-inline: 0.272rem;
border-radius: 0.272rem;
color: hsl(var(--blue) 100 69);
background-color: hsl(var(--blue) 85 35 / 0.1);
}
pre {
padding: 1rem;
margin: 0.618rem;
border-radius: 0.618rem;
overflow: auto;
text-wrap: wrap;
&>button {
all: unset;
width: 3svh;
height: 3svh;
position: absolute;
top: 0.5rem;
right: 0.5rem;
font-size: 1.618rem;
font-weight: 500;
border-radius: 0.272rem;
padding: 0.418rem;
display: flex;
align-items: center;
justify-content: center;
color: hsl(var(--blue) 100 69);
background-color: hsl(var(--blue) 85 35 / 0.1);
cursor: pointer;
transition: color 0.25s;
&:hover {
color: hsl(var(--blue) 100 75);
}
&.copied {
animation: pulseGreen 0.5s cubic-bezier(0, 1, 0.6, 1);
}
}
}

View file

@ -0,0 +1,31 @@
.typeprop-link {
color: hsl(var(--prop-link-color));
& a {
color: hsl(var(--prop-link-color)) !important;
}
}
.typesignal-link {
color: hsl(var(--signal-link-color));
& a {
color: hsl(var(--signal-link-color)) !important;
}
}
.typefunc-link {
color: hsl(var(--func-link-color));
& a {
color: hsl(var(--func-link-color)) !important;
}
}
.typevar-link {
color: hsl(var(--var-link-color));
& a {
color: hsl(var(--var-link-color)) !important;
}
}

View file

@ -0,0 +1,41 @@
.search-output {
position: fixed;
inset: 0 25svw;
top: 3.6rem;
z-index: 33;
max-height: 100svw;
max-width: 50svw;
overflow: scroll;
overflow-x: show;
display: grid;
grid-template-columns: 1fr;
grid-auto-rows: max-content;
row-gap: 0.618rem;
padding: 1rem;
}
.search-output::before {
content: "";
position: fixed;
inset: -4rem;
backdrop-filter: blur(4px);
overflow: hidden;
background-color: hsla(0 0 0 / 0.3);
}
.search-output_item {
height: max-content;
padding: 1rem;
border: 2px solid rgba(200, 200, 200, 0.23);
border-radius: 12px;
background-color: hsl(0 0 0);
overflow: hidden;
& mark {
all: unset;
background: hsla(53 800 34 / 0.5);
padding-inline: 3px;
}
}

View file

@ -0,0 +1 @@
@import "./modal.css";

View file

@ -0,0 +1,66 @@
[data-scope='collapsible'][data-part='root'] {
--height: max-content;
padding: 0.673rem;
background-color: hsl(var(--white) 40 50 / 0.1);
border-radius: 0.618rem;
}
[data-scope='collapsible'][data-part='content'] {
transition: all 250ms;
padding: 0;
margin-left: 0;
}
[data-scope='collapsible'][data-part='content'][data-state='open'] {
animation: slideDown 250ms;
display: flex;
gap: 1.272rem;
flex-direction: column;
padding: 0.618em;
margin-left: 22px;
& p {
margin-block: 0;
}
}
[data-scope='collapsible'][data-part='content'][data-state='closed'] {
animation: slideUp 200ms;
padding: 0;
margin-left: 0;
& svg {
animation: rotateOut90 250ms;
}
}
[data-scope='collapsible'][data-part='trigger'] {
all: unset;
cursor: pointer;
font-size: 1.117rem;
font-weight: 500;
display: flex;
gap: 0.272rem;
align-items: center;
justify-content: space-between;
padding: 0.618rem;
border-radius: 0.618rem;
&:hover {
background-color: hsl(var(--white) 40 50 / 0.1);
}
&[data-state='open'] {
& svg {
animation: rotateIn90 250ms forwards;
}
}
&[data-state='closed'] {
& svg {
animation: rotateOut90 250ms forwards;
}
}
}

View file

@ -0,0 +1,13 @@
.small {
opacity: 0.6;
transition: opacity 0.5s;
&:hover {
opacity: 1;
}
& p {
font-size: 0.841rem;
margin-top: 1rem;
}
}

View file

@ -0,0 +1,407 @@
.root-nav {
margin-left: 1em;
display: flex;
flex-direction: column;
& .root-nav-entry {
margin-block: 1em;
}
}
.types-nav {
display: none;
}
.typedocs-title {
& a {
opacity: 0.6;
transition: opacity 0.5s;
&:hover {
opacity: 1;
}
}
}
#injectedMd {
&>p:not(:first-child) {
margin-block: 0.724rem;
}
}
.type-module {
color: hsl(var(--blue) 75 60);
}
.typedocs-subheading {
margin-bottom: 1rem;
}
.typedocs-content {
&>p {
margin-block: 0.618rem;
}
& hr {
margin-top: 0;
margin-bottom: 0.318rem;
}
}
.typedocs-data {
& subheading {
display: flex;
flex-direction: column;
gap: 0.618rem;
}
}
.typedata {
list-style: none;
margin: 0;
& .typedata-root {
margin-bottom: 0.618rem;
border-radius: 12px;
padding: 1.272rem;
transition: border 0.3s;
&>.typedata-name {
display: flex;
align-items: center;
font-size: 1.272rem;
margin: 0;
margin-top: 0.272rem;
&>svg {
width: 27px;
height: 27px;
margin-right: 6px;
}
}
}
& .typedata-details {
margin-top: 1.618rem;
}
& .typedata-params {
& .typedata-param {
margin-top: 0.272rem;
display: flex;
align-items: center;
gap: 0.117rem;
&>svg {
height: 1.272rem;
width: 1.272rem;
}
}
}
& .typedata-detailsdata,
.typedocs-subheading {
&>p {
margin-top: 0.618rem;
}
& .typeprop-link {
color: hsl(var(--prop-link-color));
& a {
color: hsl(var(--prop-link-color));
}
}
& .typesignal-link {
color: hsl(var(--signal-link-color));
& a {
color: hsl(var(--signal-link-color));
}
}
& .typefunc-link {
color: hsl(var(--func-link-color));
& a {
color: hsl(var(--func-link-color));
}
}
& .typevar-link {
color: hsl(var(--var-link-color));
& a {
color: hsl(var(--var-link-color))
}
}
}
}
.typedata-link {
display: inline-flex;
align-items: baseline;
gap: 3px;
& svg {
width: 18px;
height: 18px;
align-self: center;
}
}
.typedata-param {
color: hsla(var(--prop-link-color) / 1);
}
.type-datatype {
color: #808080;
opacity: 0.8;
width: max-content;
transition: opacity 0.5s;
&>a {
opacity: inherit;
}
}
.type-flags {
& .type-flag {
margin: 0;
margin-top: 0.272rem;
display: flex;
align-items: center;
gap: 0.117rem;
color: hsl(var(--inner-param-color))
}
}
.typeprops {
& .typeprop-root {
border: 1px solid hsla(var(--prop-color) / 0.3);
&:hover {
border: 1px solid hsla(var(--prop-color) / 0.6);
& .type-datatype {
opacity: 1;
}
}
& .typeprop-name {
color: hsl(var(--prop-link-color));
}
}
}
.typefuncs {
& .typefunc-root {
border: 1px solid hsla(var(--func-color) / 0.3);
&:hover {
border: 1px solid hsla(var(--func-color) / 0.6);
& .type-datatype {
opacity: 1;
}
}
& .typefunc-name {
color: hsl(var(--func-link-color));
}
& .typefunc-params {
& .typefunc-param {
margin-top: 0.272rem;
display: flex;
align-items: center;
gap: 0.117rem;
&>svg {
height: 1.272rem;
width: 1.272rem;
}
}
}
}
}
.typesignals {
& .typesignal-root {
border: 1px solid hsla(var(--signal-color) / 0.3);
&:hover {
border: 1px solid hsla(var(--signal-color) / 0.6);
& .typesignal-doclink {
opacity: 1;
}
}
& .typesignal-name {
position: relative;
width: max-content;
color: hsl(var(--signal-link-color));
& .typesignal-doclink {
position: absolute;
top: -12px;
right: -12px;
opacity: 0.8;
scale: 75%;
}
}
& .typesignal-params {
& .typesignal-param {
margin-top: 0.272rem;
display: flex;
align-items: center;
gap: 0.117rem;
&>svg {
height: 1.272rem;
width: 1.272rem;
}
}
}
}
}
.typevariants {
& .typevariant-root {
border: 1px solid hsla(var(--var-color) / 0.3);
&:hover {
border: 1px solid hsla(var(--var-color) / 0.6);
& .typevariant-doclink {
opacity: 1;
}
}
& .typevariant-name {
position: relative;
width: max-content;
color: hsl(var(--var-link-color));
& .typevariant-doclink {
position: absolute;
top: -12px;
right: -12px;
opacity: 0.8;
scale: 75%;
}
}
}
}
@media (min-width: 768px) {
.typedocs-content {
margin-inline: 1.272rem;
& section {
min-width: 30svw;
}
&>p {
margin-block: 1.217rem;
}
}
}
@media (min-width: 1280px) {
.typedata {
max-width: 47svw;
}
.typedata-detailsdata,
.typedocs-subheading {
max-width: 42svw;
}
.root-nav {
min-width: 45svw;
}
.types-nav {
display: block;
position: fixed;
top: 5rem;
right: 10svw;
width: 250px;
max-height: 90svh;
overflow-y: scroll;
z-index: 10;
& .props-list {
color: hsl(var(--prop-color));
& a {
color: hsl(var(--prop-link-color));
}
}
& .funcs-list {
color: hsl(var(--func-color));
& a {
color: hsl(var(--func-link-color));
}
}
& .signals-list {
color: hsl(var(--signal-color));
& a {
color: hsl(var(--signal-link-color));
}
}
& .vars-list {
color: hsl(var(--var-color));
& a {
color: hsl(var(--var-link-color));
}
}
& .types-list {
list-style: none;
& .types-item {
margin-block: 10px;
display: flex;
align-items: center;
gap: 0.478rem;
& svg {
opacity: 0.6;
width: 24px;
height: 24px;
transition: opacity 0.5s;
}
&:hover {
& svg {
opacity: 1;
}
}
}
}
}
}

124
src/styles/docs/docs.css Normal file
View file

@ -0,0 +1,124 @@
@import "./docs-config.css";
@import "./docs-types.css";
.docslayout-root {
margin: 0.618rem;
margin-top: 3.5rem;
overflow: hidden;
}
.docs,
.docslayout-root {
display: flex;
flex-direction: column;
}
.spacer-desktop {
display: none;
}
.c-breadcrumbs {
margin-top: 1.056rem;
margin-bottom: 0.318rem;
max-width: 100svw;
}
.docs-content {
& section {
max-width: 95svw;
margin-block: 1.618rem;
}
& p {
margin-block: 0.618rem;
}
& hr {
margin-top: 0;
margin-bottom: 0.318rem;
}
}
.heading {
& > [id] {
width: max-content;
}
& .heading-hashtag {
& svg {
width: 24px;
height: 24px;
opacity: 0.5;
transition: opacity 0.5s;
&:hover {
opacity: 1;
cursor: pointer;
}
}
}
}
hr {
opacity: 0.3;
}
ul {
margin-left: 2.478rem;
}
.markdown-alert {
margin-block: 0.618rem;
& > *:not(:first-child) {
margin-block: 0.724rem;
}
}
.markdown-alert-title {
text-transform: lowercase;
text-transform: capitalize;
}
@media (min-width: 768px) {
.docs,
.docslayout-root {
gap: 0.648rem;
}
.docslayout-root {
margin-left: calc(1.618rem + 260px);
}
.docslayout-inner {
display: block;
}
.docs-content {
margin-inline: 1.272rem;
& section {
margin-block: 1.884rem;
}
& p {
margin-block: 1.217rem;
}
}
.c-breadcrumbs {
margin-inline: 1.272rem;
}
}
@media (min-width: 1280px) {
.docs-content {
& section {
max-width: 45svw;
}
}
.docslayout-root {
margin-inline: calc(10svw + 260px);
}
}

View file

@ -0,0 +1,171 @@
[data-scope="accordion"][data-part="root"] {
display: flex;
flex-direction: column;
gap: 0.15em;
& [data-part="item"] {
padding: 6px;
& [data-part="item"] {
padding-right: 0;
}
}
}
[data-scope="accordion"][data-part="item-trigger"] {
background-color: hsl(var(--bg-900));
position: relative;
border: unset;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
width: 100%;
height: 36px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 0.15em;
& p:has(a) {
text-overflow: ellipsis;
overflow: hidden;
padding-inline: 4px;
border-radius: 20px;
&:hover {
position: relative;
width: max-content;
padding: 4px;
overflow: scroll;
z-index: 101;
}
}
&::before {
content: "";
position: absolute;
border-radius: 6px;
z-index: -1;
inset: -1px;
background-color: hsla(var(--green) 80 70 / 0.3);
transition: background-color 0.3s;
}
&:hover {
&::before {
background-color: hsla(var(--green) 80 70 / 0.6);
}
}
}
[data-scope="accordion"][data-part="item-indicator"] {
position: relative;
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
border-radius: 50%;
margin-left: 3px;
&:hover {
background-color: hsl(var(--blue) 30 30);
}
}
[data-scope="accordion"][data-part="item-indicator"][data-state="open"] {
animation: rotateIn 250ms ease-in-out forwards;
}
[data-scope="accordion"][data-part="item-indicator"][data-state="closed"] {
animation: rotateOut 250ms ease-in-out;
}
[data-scope="accordion"][data-part="item-content"] {
--height: 709;
margin-block: 0.175rem;
&>.arktree-item,
[data-part="item"] {
margin-left: 21px;
margin-block: 0.117rem;
}
& .arktree-item,
[data-part="item-content"]>div {
display: flex;
flex-direction: column;
gap: 0.15em;
margin-left: 24px;
margin-top: 0.224em;
&::before {
content: "";
position: absolute;
bottom: 0;
height: 1px;
background: linear-gradient(to right,
hsla(var(--accent-400) / 0.5) var(--percent),
hsla(var(--accent-400) / 0) 75%);
animation: percentToZero 250ms ease-in-out forwards;
width: 0;
transition: width 0.25s;
}
&:hover::before {
width: 100%;
animation: percentToFifty 250ms ease-in-out forwards;
}
&>a {
padding-top: 1em;
width: 100%;
&:hover {
text-decoration: none;
}
}
}
}
[data-scope="accordion"][data-part="item-content"][data-state="open"] {
animation: slideDown 250ms ease-in-out;
}
[data-scope="accordion"][data-part="item-content"][data-state="closed"] {
animation: slideUp 200ms ease-in-out;
}
.__current-type-doc {
color: hsl(var(--blue) 100 70);
& [data-part="item-trigger"] a {
color: hsl(var(--blue) 100 70) !important;
}
&>a {
&::before {
content: "";
position: absolute;
bottom: 0;
height: 1px;
background: linear-gradient(to right,
hsla(var(--accent-400) / 0.5) var(--percent),
hsla(var(--accent-400) / 0) 100%);
animation: percentToZero 250ms ease-in-out forwards;
width: 100%;
}
&:hover::before {
animation: percentToFifty 250ms ease-in-out forwards;
}
}
}

127
src/styles/docs/nav/nav.css Normal file
View file

@ -0,0 +1,127 @@
@import "./nav-tree.css";
.nav-wrapper {
display: none;
}
.nav-toggle {
position: unset;
height: 24px;
font-size: 1.614rem;
overflow-y: scroll;
max-height: 500px;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
& svg,
div {
height: max-content;
width: 100%;
}
&:hover {
cursor: pointer;
}
& .nav-items {
position: absolute;
z-index: 11;
overflow-y: scroll;
top: 2.5rem;
left: -1rem;
width: 0;
height: 0;
font-size: 0.745rem;
font-weight: 600;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
transition:
width 0.3s ease,
height 0.3s ease,
background-color 0.3s ease,
backdrop-filter 0.3s ease,
padding 0.3s ease;
&.shown {
padding: 0.3rem;
width: 100svw;
height: 50svh;
background-color: hsl(var(--bg-900) / 0.6);
backdrop-filter: blur(3px) saturate(180%);
display: flex;
flex-direction: column;
align-items: center;
}
& .navtree {
height: 100%;
width: 100%;
overflow: scroll;
padding: 6px;
& [data-part="item"] {
margin-left: unset;
display: flex;
flex-direction: column;
align-items: flex-start;
padding-right: 0;
& [data-part="item-content"] {
& [data-part="item-trigger"] {
width: 93%;
margin-left: 1em;
& p {
text-align: left;
}
}
& [data-part="item-content"] {
width: 80%;
margin-left: 24px;
}
}
}
}
}
}
@media (min-width: 768px) {
.nav-wrapper-mobile {
display: none;
}
.nav-wrapper {
display: block;
width: 250px;
position: fixed;
top: 5rem;
left: 1.618rem;
flex-shrink: 0;
overflow: scroll;
max-height: 90svh;
scrollbar-width: none;
-ms-overflow-style: none;
z-index: 10;
}
.navtree {
width: 100%;
z-index: 1;
}
}
@media (min-width: 1280px) {
.nav-wrapper {
left: 10svw;
}
}

View file

@ -0,0 +1,29 @@
.toc-wrapper-mobile .toc-content {
& .toc_a {
transition: color 0.33s;
color: hsl(var(--green) 72 40);
}
& ul {
margin: 0;
}
& li {
list-style: none;
&.active {
& > .toc_a {
color: hsl(var(--green) 72 60);
}
}
}
& .toc_heading {
margin: 0;
margin-block: 0.618rem;
& * {
margin-left: 0.348rem;
}
}
}

125
src/styles/docs/toc/toc.css Normal file
View file

@ -0,0 +1,125 @@
@import "./types-toc.css";
@import "./intro-toc.css";
.toc-wrapper {
display: none;
}
.toc-wrapper-mobile {
display: block;
}
.toc-toggle {
--width: 100svw;
display: block;
position: unset;
height: 24px;
font-size: 1.614rem;
max-height: 500px;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
& > svg {
height: 100%;
width: 24px;
}
& div {
width: 100%;
height: max-content;
}
&:hover {
cursor: pointer;
}
& .toc-mobile {
position: absolute;
overflow-y: scroll;
top: 2.6rem;
right: -1rem;
width: 0;
height: 0;
font-size: 0.745rem;
font-weight: 600;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
transition:
width 0.3s ease,
height 0.3s ease,
background-color 0.3s ease,
backdrop-filter 0.3s ease,
padding 0.3s ease;
&.shown {
padding: 0.3rem;
width: var(--width);
height: 50svh;
background-color: hsl(var(--bg-900) / 0.6);
backdrop-filter: blur(3px) saturate(180%);
display: flex;
flex-direction: column;
align-items: center;
}
}
}
@media (max-width: 1280px) {
.toc-toggle {
--width: 70svw;
}
}
@media (min-width: 1280px) {
.toc-wrapper-mobile {
display: none;
}
.toc-wrapper {
display: block;
position: fixed;
top: 5rem;
right: 10svw;
width: 250px;
max-height: 90svh;
overflow-y: scroll;
z-index: 10;
& .toc_a {
transition: color 0.33s;
color: hsl(var(--green) 72 40);
}
& ul {
margin: 0;
}
& li {
list-style: none;
&.active {
& > .toc_a {
color: hsl(var(--green) 72 60);
}
}
}
& .toc_heading {
margin: 0;
margin-block: 0.618rem;
& * {
margin-left: 0.348rem;
}
}
}
}

View file

@ -0,0 +1,63 @@
.toc-content {
height: 100%;
width: 100%;
overflow: scroll;
padding: 6px;
& .props-list {
color: hsl(var(--prop-color));
& a {
color: hsl(var(--prop-link-color));
}
}
& .funcs-list {
color: hsl(var(--func-color));
& a {
color: hsl(var(--func-link-color));
}
}
& .signals-list {
color: hsl(var(--signal-color));
& a {
color: hsl(var(--signal-link-color));
}
}
& .vars-list {
color: hsl(var(--var-color));
& a {
color: hsl(var(--var-link-color));
}
}
}
.types-list {
list-style: none;
z-index: 21;
& .types-item {
margin-block: 10px;
display: flex;
align-items: center;
gap: 0.478rem;
& svg {
opacity: 0.6;
width: 24px;
height: 24px;
transition: opacity 0.5s;
}
&:hover {
& svg {
opacity: 1;
}
}
}
}

142
src/styles/global.css Normal file
View file

@ -0,0 +1,142 @@
@import "remark-github-blockquote-alert/alert.css";
@import "./css-config/base.css";
@import "./css-config/animations.css";
@import "./css-config/code.css";
@import "./css-config/search.css";
@import "./css-config/colors.css";
@import "./docs/nav/nav.css";
@import "./docs/toc/toc.css";
@import "./docs/docs.css";
@import "./docs/collapsible.css";
/* color styling */
.header {
background-color: hsl(var(--blue) 100 88);
box-shadow: 0 3px 3px 3px hsla(var(--white) 100 0 / 0.3);
}
.baselayout,
.docslayout {
background-color: hsl(var(--bg-500));
color: hsl(var(--secondary-900));
}
a {
color: hsla(var(--accent-600));
text-decoration: none;
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
html.dark {
& .header {
background-color: hsl(var(--secondary-900));
color: hsl(var(--secondary-500));
}
& .baselayout,
.docslayout {
background-color: hsl(var(--bg-900));
color: hsl(var(--secondary-400));
}
& a {
color: hsl(var(--accent-500));
}
}
/* layout and positioning */
.unset {
all: unset;
}
.search {
display: none;
}
.header {
position: fixed;
top: 0;
width: 100%;
z-index: 10;
display: flex;
align-items: center;
height: 3.5rem;
}
.header-title {
display: none;
font-size: 1.614em;
font-weight: 700;
line-height: 1.272;
}
.header-title.mobile {
display: block;
}
.header-item {
display: flex;
gap: 1rem;
flex-shrink: 0;
flex-grow: 1;
align-items: center;
}
.header-left {
justify-content: flex-start;
padding-left: 1rem;
}
.header-right {
justify-content: flex-end;
padding-right: 1rem;
}
.spacer-mobile {
font-size: 1.374rem;
color: hsla(var(--white) 40 50 / 0.3);
}
.theme-toggle {
height: 24px;
font-size: 1.614rem;
&:hover {
cursor: pointer;
}
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
.header-title {
display: block;
}
.header-title.mobile {
display: none;
}
.menu-toggle,
.spacer-mobile {
display: none;
}
.search {
display: block;
}
.spacer-desktop {
display: block;
font-size: 1.374rem;
color: hsla(var(--white) 40 50 / 0.3);
}
}
@media (min-width: 1280px) {}

37
tsconfig.json Normal file
View file

@ -0,0 +1,37 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"plugins": [
{
"name": "@astrojs/ts-plugin"
}
],
"jsx": "preserve",
"jsxImportSource": "solid-js",
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {
"@*": [
"./*"
],
"@/*": [
"./src/*"
],
"@config/*": [
"./src/config/*"
],
"@icons": [
"./src/components/icons.tsx"
],
"@components/*": [
"./src/components/*"
],
"@layouts/*": [
"./src/layouts/*"
],
"@styles/*": [
"./src/styles/*"
]
}
}
}

8245
yarn.lock Normal file

File diff suppressed because it is too large Load diff