docs-site: implement tabs
This commit is contained in:
@@ -1,11 +1,14 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
%sveltekit.head%
|
<script>
|
||||||
</head>
|
document.documentElement.classList.add("js");
|
||||||
<body data-sveltekit-preload-data="hover">
|
</script>
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
%sveltekit.head%
|
||||||
</body>
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@import url("./shiki.css");
|
@import url("./shiki.css");
|
||||||
@import url("./admonition.css");
|
@import url("./admonition.css");
|
||||||
|
@import url("./tabs.css");
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family:
|
font-family:
|
||||||
|
|||||||
50
pkgs/docs-site/src/lib/markdown/tabs.css
Normal file
50
pkgs/docs-site/src/lib/markdown/tabs.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
.md-tabs-bar {
|
||||||
|
display: none;
|
||||||
|
gap: 7px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
.md-tabs-tab {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
.md-tabs-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.js {
|
||||||
|
.md-tabs-bar {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.md-tabs-container {
|
||||||
|
margin: 0;
|
||||||
|
> .md-tabs-tab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.md-tabs {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.md-tabs-tab {
|
||||||
|
background: #d7dadf;
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
&.is-active {
|
||||||
|
background: #eff1f5;
|
||||||
|
|
||||||
|
.md-tabs.is-singleton & {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 1px solid #d8dbe1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.md-tabs-content {
|
||||||
|
display: none;
|
||||||
|
margin: 0 var(--pageMargin);
|
||||||
|
&.is-active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import rehypeTocSlug from "./rehype-toc-slug";
|
|||||||
import transformerLineNumbers from "./shiki-transformer-line-numbers";
|
import transformerLineNumbers from "./shiki-transformer-line-numbers";
|
||||||
import remarkParse from "./remark-parse";
|
import remarkParse from "./remark-parse";
|
||||||
import remarkAdmonition from "./remark-admonition";
|
import remarkAdmonition from "./remark-admonition";
|
||||||
|
import remarkTabs from "./remark-tabs";
|
||||||
import rehypeWrapHeadings from "./rehype-wrap-headings";
|
import rehypeWrapHeadings from "./rehype-wrap-headings";
|
||||||
import remarkLinkMigration from "./link-migration";
|
import remarkLinkMigration from "./link-migration";
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ export default function ({
|
|||||||
.use(remarkGfm)
|
.use(remarkGfm)
|
||||||
.use(remarkDirective)
|
.use(remarkDirective)
|
||||||
.use(remarkAdmonition)
|
.use(remarkAdmonition)
|
||||||
|
.use(remarkTabs)
|
||||||
.use(remarkRehype)
|
.use(remarkRehype)
|
||||||
.use(rehypeTocSlug, {
|
.use(rehypeTocSlug, {
|
||||||
tocMaxDepth,
|
tocMaxDepth,
|
||||||
|
|||||||
93
pkgs/docs-site/src/lib/markdown/vite/remark-tabs.ts
Normal file
93
pkgs/docs-site/src/lib/markdown/vite/remark-tabs.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { visit } from "unist-util-visit";
|
||||||
|
import type { Paragraph, Root, Text } from "mdast";
|
||||||
|
|
||||||
|
export default function remarkTabs() {
|
||||||
|
return (tree: Root) => {
|
||||||
|
visit(tree, (node) => {
|
||||||
|
if (node.type != "containerDirective" || node.name != "tabs") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (node.data ||= {});
|
||||||
|
data.hName = "div";
|
||||||
|
data.hProperties = {
|
||||||
|
className: "md-tabs",
|
||||||
|
};
|
||||||
|
let tabIndex = 0;
|
||||||
|
let tabTitles: string[] = [];
|
||||||
|
for (const [i, child] of node.children.entries()) {
|
||||||
|
if (child.type != "containerDirective" || child.name != "tab") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let tabTitle: string;
|
||||||
|
if (child.children?.[0].data?.directiveLabel) {
|
||||||
|
const p = child.children.shift() as Paragraph;
|
||||||
|
tabTitle = (p.children[0] as Text).value;
|
||||||
|
} else {
|
||||||
|
tabTitle = "(empty)";
|
||||||
|
}
|
||||||
|
tabTitles.push(tabTitle);
|
||||||
|
node.children[i] = {
|
||||||
|
type: "containerDirective",
|
||||||
|
name: "",
|
||||||
|
data: {
|
||||||
|
hName: "div",
|
||||||
|
hProperties: {
|
||||||
|
className: "md-tabs-container",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "paragraph",
|
||||||
|
data: {
|
||||||
|
hName: "div",
|
||||||
|
hProperties: {
|
||||||
|
className: `md-tabs-tab ${tabIndex == 0 ? "is-active" : ""}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [{ type: "text", value: tabTitle }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "containerDirective",
|
||||||
|
name: "",
|
||||||
|
data: {
|
||||||
|
hName: "div",
|
||||||
|
hProperties: {
|
||||||
|
className: `md-tabs-content ${tabIndex == 0 ? "is-active" : ""}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: child.children,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
tabIndex++;
|
||||||
|
}
|
||||||
|
if (tabTitles.length === 1) {
|
||||||
|
data.hProperties.className += " is-singleton";
|
||||||
|
}
|
||||||
|
// Add tab bar for when js is enabled
|
||||||
|
node.children = [
|
||||||
|
{
|
||||||
|
type: "paragraph",
|
||||||
|
data: {
|
||||||
|
hName: "div",
|
||||||
|
hProperties: {
|
||||||
|
className: "md-tabs-bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: tabTitles.map((tabTitle, tabIndex) => ({
|
||||||
|
type: "text",
|
||||||
|
data: {
|
||||||
|
hName: "div",
|
||||||
|
hProperties: {
|
||||||
|
className: `md-tabs-tab ${tabIndex == 0 ? "is-active" : ""}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: tabTitle,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
...node.children,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 var(--pagePadding);
|
padding: 0 var(--pageMargin);
|
||||||
color: var(--fgInvertedColor);
|
color: var(--fgInvertedColor);
|
||||||
background: var(--bgInvertedColor);
|
background: var(--bgInvertedColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "$lib/markdown/main.css";
|
import "$lib/markdown/main.css";
|
||||||
import { visit, type Heading as ArticleHeading } from "$lib/docs";
|
import { visit, type Heading as ArticleHeading } from "$lib/docs";
|
||||||
|
import { onMount } from "svelte";
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
|
|
||||||
type Heading = ArticleHeading & {
|
type Heading = ArticleHeading & {
|
||||||
@@ -39,6 +40,36 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const onClick = (ev: MouseEvent) => {
|
||||||
|
const targetTabEl = (ev.target as HTMLElement).closest(".md-tabs-tab");
|
||||||
|
if (!targetTabEl || targetTabEl.classList.contains(".is-active")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tabsEl = targetTabEl.closest(".md-tabs")!;
|
||||||
|
const tabEls = tabsEl.querySelectorAll(".md-tabs-tab")!;
|
||||||
|
const tabIndex = Array.from(tabEls).indexOf(targetTabEl);
|
||||||
|
if (tabIndex == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tabContentEls = tabsEl.querySelectorAll(".md-tabs-content");
|
||||||
|
const tabContentEl = tabContentEls[tabIndex];
|
||||||
|
if (!tabContentEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tabEls.forEach((tabEl) => tabEl.classList.remove("is-active"));
|
||||||
|
targetTabEl.classList.add("is-active");
|
||||||
|
tabContentEls.forEach((tabContentEl) =>
|
||||||
|
tabContentEl.classList.remove("is-active"),
|
||||||
|
);
|
||||||
|
tabContentEl.classList.add("is-active");
|
||||||
|
};
|
||||||
|
document.addEventListener("click", onClick);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("click", onClick);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
function normalizeHeadings(headings: ArticleHeading[]): Heading[] {
|
function normalizeHeadings(headings: ArticleHeading[]): Heading[] {
|
||||||
return headings.map((heading) => ({
|
return headings.map((heading) => ({
|
||||||
...heading,
|
...heading,
|
||||||
@@ -232,7 +263,7 @@
|
|||||||
|
|
||||||
:global {
|
:global {
|
||||||
& :is(h1, h2, h3, h4, h5, h6) {
|
& :is(h1, h2, h3, h4, h5, h6) {
|
||||||
margin-left: calc(-1 * var(--pagePadding));
|
margin-left: calc(-1 * var(--pageMargin));
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
&.is-scrolledPast {
|
&.is-scrolledPast {
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ See the complete [list](../guides/inventory/autoincludes.md) of auto-loaded file
|
|||||||
|
|
||||||
## Create a machine
|
## Create a machine
|
||||||
|
|
||||||
=== "clan.nix (declarative)"
|
::::tabs
|
||||||
|
|
||||||
|
:::tab[clan.nix (declarative)]
|
||||||
|
|
||||||
```nix {3-4}
|
```nix {3-4}
|
||||||
{
|
{
|
||||||
@@ -28,7 +30,9 @@ See the complete [list](../guides/inventory/autoincludes.md) of auto-loaded file
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "CLI (imperative)"
|
:::
|
||||||
|
|
||||||
|
:::tab[CLI (imperative)]
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
clan machines create jon
|
clan machines create jon
|
||||||
@@ -36,6 +40,23 @@ clan machines create jon
|
|||||||
|
|
||||||
The imperative command might create a machine folder in `machines/jon`
|
The imperative command might create a machine folder in `machines/jon`
|
||||||
And might persist information in `inventory.json`
|
And might persist information in `inventory.json`
|
||||||
|
:::
|
||||||
|
::::
|
||||||
|
|
||||||
|
::::tabs
|
||||||
|
|
||||||
|
:::tab[file name test]
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inventory.machines = {
|
||||||
|
# Define a machine
|
||||||
|
jon = { };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
::::
|
||||||
|
|
||||||
### Configuring a machine
|
### Configuring a machine
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@import "@fontsource-variable/geist";
|
@import "@fontsource-variable/geist";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--pagePadding: 15px;
|
--pageMargin: 15px;
|
||||||
--globalBarHeight: 60px;
|
--globalBarHeight: 60px;
|
||||||
--fgColor: #000;
|
--fgColor: #000;
|
||||||
--fgInvertedColor: #fff;
|
--fgInvertedColor: #fff;
|
||||||
|
|||||||
Reference in New Issue
Block a user