andesine 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +27 -8
- package/dist/cli.js +1611 -121
- package/dist/cli.js.map +1 -1
- package/dist/config.schema.json +20 -3
- package/dist/templates/pages/.env.example +5 -0
- package/dist/templates/pages/AGENTS.md +79 -0
- package/dist/templates/pages/LICENSE +21 -0
- package/dist/templates/pages/README.md +72 -0
- package/dist/templates/pages/andesine.config.ts +39 -0
- package/dist/templates/pages/astro.config.ts +23 -0
- package/dist/templates/pages/gitignore +13 -0
- package/dist/templates/pages/package.template.json +45 -0
- package/dist/templates/pages/src/api/openapi.yaml +246 -0
- package/dist/templates/pages/src/assets/andesine.svg +1 -0
- package/dist/templates/pages/src/assets/andesine.ts +6 -0
- package/dist/templates/pages/src/assets/favicon.svg +13 -0
- package/dist/templates/pages/src/assets/social-dots.svg +14 -0
- package/dist/templates/pages/src/components/content/callout.astro +26 -0
- package/dist/templates/pages/src/components/content/card-grid.astro +1 -0
- package/dist/templates/pages/src/components/content/card.astro +31 -0
- package/dist/templates/pages/src/components/content/code-block.astro +34 -0
- package/dist/templates/pages/src/components/content/code-group.astro +13 -0
- package/dist/templates/pages/src/components/content/copy-button.tsx +53 -0
- package/dist/templates/pages/src/components/content/disclosure.astro +26 -0
- package/dist/templates/pages/src/components/content/figure.astro +10 -0
- package/dist/templates/pages/src/components/content/file-tree.astro +47 -0
- package/dist/templates/pages/src/components/content/index.ts +30 -0
- package/dist/templates/pages/src/components/content/steps.astro +16 -0
- package/dist/templates/pages/src/components/content/tab.astro +7 -0
- package/dist/templates/pages/src/components/content/tabs.astro +15 -0
- package/dist/templates/pages/src/components/content/tabs.tsx +99 -0
- package/dist/templates/pages/src/components/effects/page-scroll-shadow.tsx +27 -0
- package/dist/templates/pages/src/components/effects/page-scrollbar.tsx +35 -0
- package/dist/templates/pages/src/components/layout/call-to-action.tsx +28 -0
- package/dist/templates/pages/src/components/layout/footer.astro +44 -0
- package/dist/templates/pages/src/components/layout/group-label.tsx +34 -0
- package/dist/templates/pages/src/components/layout/header.astro +53 -0
- package/dist/templates/pages/src/components/layout/mobile-navigation.tsx +51 -0
- package/dist/templates/pages/src/components/layout/navigation-links.tsx +57 -0
- package/dist/templates/pages/src/components/layout/navigation-tree.tsx +138 -0
- package/dist/templates/pages/src/components/layout/outline.tsx +162 -0
- package/dist/templates/pages/src/components/layout/page-actions.tsx +87 -0
- package/dist/templates/pages/src/components/layout/powered-by.tsx +29 -0
- package/dist/templates/pages/src/components/layout/section-tabs.tsx +83 -0
- package/dist/templates/pages/src/components/layout/sidebar.tsx +65 -0
- package/dist/templates/pages/src/components/primitives/button.tsx +121 -0
- package/dist/templates/pages/src/components/primitives/card.tsx +25 -0
- package/dist/templates/pages/src/components/primitives/dialog.tsx +47 -0
- package/dist/templates/pages/src/components/primitives/input.tsx +22 -0
- package/dist/templates/pages/src/components/primitives/logo.tsx +41 -0
- package/dist/templates/pages/src/components/primitives/menu.tsx +121 -0
- package/dist/templates/pages/src/components/primitives/scroll-area.tsx +103 -0
- package/dist/templates/pages/src/components/primitives/shortcut.tsx +60 -0
- package/dist/templates/pages/src/components/primitives/tooltip.tsx +39 -0
- package/dist/templates/pages/src/components/reference/facts.astro +27 -0
- package/dist/templates/pages/src/components/reference/fields.astro +102 -0
- package/dist/templates/pages/src/components/reference/method-badge.astro +18 -0
- package/dist/templates/pages/src/components/reference/methods.ts +17 -0
- package/dist/templates/pages/src/components/reference/operation.astro +252 -0
- package/dist/templates/pages/src/components/reference/styles.ts +14 -0
- package/dist/templates/pages/src/components/search/highlighted-text.tsx +22 -0
- package/dist/templates/pages/src/components/search/search-answer.tsx +123 -0
- package/dist/templates/pages/src/components/search/search-palette.tsx +174 -0
- package/dist/templates/pages/src/components/search/search-results.tsx +229 -0
- package/dist/templates/pages/src/components/social/card.ts +77 -0
- package/dist/templates/pages/src/content/docs/customization/index.mdx +11 -0
- package/dist/templates/pages/src/content/docs/customization/styles.mdx +18 -0
- package/dist/templates/pages/src/content/docs/getting-started/api-reference.mdx +100 -0
- package/dist/templates/pages/src/content/docs/getting-started/components.mdx +156 -0
- package/dist/templates/pages/src/content/docs/getting-started/configuration.mdx +194 -0
- package/dist/templates/pages/src/content/docs/getting-started/deployment.mdx +142 -0
- package/dist/templates/pages/src/content/docs/getting-started/index.mdx +22 -0
- package/dist/templates/pages/src/content/docs/getting-started/writing-pages.mdx +35 -0
- package/dist/templates/pages/src/content/docs/index.mdx +21 -0
- package/dist/templates/pages/src/content.config.ts +1 -0
- package/dist/templates/pages/src/layouts/page.astro +195 -0
- package/dist/templates/pages/src/styles/global.css +95 -0
- package/dist/templates/pages/tsconfig.json +10 -0
- package/dist/templates/pages/uno.config.ts +168 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<div class="my-2 grid gap-4 sm:grid-cols-2"><slot /></div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
title: string;
|
|
4
|
+
/** Card text; the slot content can be used instead. */
|
|
5
|
+
description?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
/** Makes the whole card a link. */
|
|
8
|
+
href?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { title, description, icon, href } = Astro.props;
|
|
12
|
+
const Element = href ? "a" : "div";
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<Element
|
|
16
|
+
href={href}
|
|
17
|
+
class:list={[
|
|
18
|
+
"not-prose flex flex-col gap-2 rounded-xl border border-gray-200 bg-white p-4 shadow-[0_0_12px_0px] shadow-gray-200",
|
|
19
|
+
href && "transition duration-200 ease-out @hover:bg-gray-100 focus-visible:bg-gray-100"
|
|
20
|
+
]}
|
|
21
|
+
>
|
|
22
|
+
{icon && <span aria-hidden="true" class:list={["h-6 w-6 bg-gradient-to-tr", icon]} />}
|
|
23
|
+
<span class="flex items-center gap-1 font-semibold text-gray-900">
|
|
24
|
+
{title}
|
|
25
|
+
{href && <span aria-hidden="true" class="i-lucide:arrow-right h-4 w-4 text-gray-400" />}
|
|
26
|
+
</span>
|
|
27
|
+
<div class="text-sm text-gray-500 [&>p]:m-0">
|
|
28
|
+
{description && <p>{description}</p>}
|
|
29
|
+
<slot />
|
|
30
|
+
</div>
|
|
31
|
+
</Element>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ScrollArea } from "../primitives/scroll-area";
|
|
3
|
+
import { CopyButton } from "./copy-button";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
"data-language"?: string;
|
|
7
|
+
"data-title"?: string;
|
|
8
|
+
[attribute: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Shiki's inline background and class are replaced by the card styles.
|
|
12
|
+
const {
|
|
13
|
+
"data-language": language,
|
|
14
|
+
"data-title": title,
|
|
15
|
+
"class": _class,
|
|
16
|
+
"style": _style,
|
|
17
|
+
...rest
|
|
18
|
+
} = Astro.props;
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<figure
|
|
22
|
+
data-code
|
|
23
|
+
class="not-prose my-2 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-[0_0_12px_0px] shadow-gray-200 [[data-code-group]_&]:(my-0 rounded-none border-none shadow-none)"
|
|
24
|
+
>
|
|
25
|
+
<figcaption
|
|
26
|
+
class="flex h-9 items-center justify-between gap-2 border-b border-gray-200 pl-3 pr-1 [[data-code-group]_&]:hidden"
|
|
27
|
+
>
|
|
28
|
+
<span class="truncate font-mono text-xs text-gray-500">{title ?? language}</span>
|
|
29
|
+
<CopyButton client:visible />
|
|
30
|
+
</figcaption>
|
|
31
|
+
<ScrollArea client:visible orientation="horizontal">
|
|
32
|
+
<pre {...rest} class="p-4 font-mono text-sm leading-6 text-gray-700"><slot /></pre>
|
|
33
|
+
</ScrollArea>
|
|
34
|
+
</figure>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderTabs } from "@andesine/pages/astro";
|
|
3
|
+
import { ContentTabs } from "./tabs";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Code labels, added by the Andesine MDX plugin or content renderer. */
|
|
7
|
+
values: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { values, panels } = await renderTabs(Astro.props.values, Astro.slots);
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<ContentTabs client:visible values={values} panels={panels} syncKey="code" variant="code" />
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createClipboard } from "@andesine/ui/solid";
|
|
2
|
+
import type { Component } from "solid-js";
|
|
3
|
+
import { IconButton } from "../primitives/button";
|
|
4
|
+
|
|
5
|
+
interface CopyButtonProps {
|
|
6
|
+
/** Copied in place of the closest code block. */
|
|
7
|
+
text?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Copies `text`, or the visible code block inside the closest `[data-code]` element. */
|
|
12
|
+
const CopyButton: Component<CopyButtonProps> = (props) => {
|
|
13
|
+
const clipboard = createClipboard();
|
|
14
|
+
const icon = (): string => {
|
|
15
|
+
if (clipboard.status() === true) return "i-lucide:check";
|
|
16
|
+
if (clipboard.status() === false) return "i-lucide:x";
|
|
17
|
+
|
|
18
|
+
return "i-lucide:copy";
|
|
19
|
+
};
|
|
20
|
+
const copy = (event: MouseEvent): void => {
|
|
21
|
+
if (props.text !== undefined) {
|
|
22
|
+
void clipboard.copy(props.text);
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const root = (event.currentTarget as HTMLElement).closest("[data-code], [data-code-group]");
|
|
28
|
+
const pre = Array.from(root?.querySelectorAll("pre") ?? []).find((element) => {
|
|
29
|
+
return element.getClientRects().length > 0;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (pre) void clipboard.copy(pre.textContent ?? "");
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<IconButton
|
|
38
|
+
icon={icon()}
|
|
39
|
+
variant="text"
|
|
40
|
+
size="xs"
|
|
41
|
+
text="softer"
|
|
42
|
+
aria-label={props.label ?? "Copy code"}
|
|
43
|
+
onClick={copy}
|
|
44
|
+
/>
|
|
45
|
+
<span role="status" class="sr-only">
|
|
46
|
+
{clipboard.status() === true ? "Copied" : ""}
|
|
47
|
+
{clipboard.status() === false ? "Copy failed" : ""}
|
|
48
|
+
</span>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export { CopyButton };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
title: string;
|
|
4
|
+
open?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { title, open } = Astro.props;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<details
|
|
11
|
+
open={open}
|
|
12
|
+
class="my-2 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-[0_0_12px_0px] shadow-gray-200"
|
|
13
|
+
>
|
|
14
|
+
<summary
|
|
15
|
+
class="not-prose flex cursor-pointer list-none items-center justify-between gap-2 min-h-9 px-4 py-1.5 font-semibold text-gray-900 transition duration-200 ease-out @hover:bg-gray-100 focus-visible:bg-gray-100 [[open]>&]:bg-gray-100 [&::-webkit-details-marker]:hidden"
|
|
16
|
+
>
|
|
17
|
+
{title}
|
|
18
|
+
<span
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
class="i-lucide:chevron-right h-4.5 w-4.5 shrink-0 text-gray-400 transition-transform duration-200 [[open]>summary>&]:rotate-90"
|
|
21
|
+
></span>
|
|
22
|
+
</summary>
|
|
23
|
+
<div class="border-t border-gray-200 px-4 py-3 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
|
|
24
|
+
<slot />
|
|
25
|
+
</div>
|
|
26
|
+
</details>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { FileTreeItem } from "@andesine/pages";
|
|
3
|
+
import { readFileTreeItems } from "@andesine/pages/astro";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Items, added by the Andesine MDX plugin; nested levels get their items. */
|
|
7
|
+
items: string | FileTreeItem[];
|
|
8
|
+
nested?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Matches the app's explorer: 1.75rem rows, 24px gray icons, and a guide line per level.
|
|
12
|
+
const { nested } = Astro.props;
|
|
13
|
+
const items = readFileTreeItems(Astro.props.items);
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<ul class:list={["not-prose flex flex-col gap-0.5 font-medium text-gray-700", !nested && "my-2"]}>
|
|
17
|
+
{
|
|
18
|
+
items.map((item) => (
|
|
19
|
+
<li class="flex flex-col">
|
|
20
|
+
<div class="flex min-h-7 items-center gap-1 pl-0.5">
|
|
21
|
+
<span class="flex h-6 w-6 shrink-0 items-center justify-center">
|
|
22
|
+
<span
|
|
23
|
+
aria-hidden="true"
|
|
24
|
+
class:list={[
|
|
25
|
+
"h-6 w-6 text-gray-400",
|
|
26
|
+
item.type === "folder"
|
|
27
|
+
? "i-material-symbols:folder-open-rounded"
|
|
28
|
+
: "i-lucide:file-text"
|
|
29
|
+
]}
|
|
30
|
+
/>
|
|
31
|
+
</span>
|
|
32
|
+
<span class="line-clamp-1 flex-1">{item.name}</span>
|
|
33
|
+
</div>
|
|
34
|
+
{item.children.length > 0 && (
|
|
35
|
+
<div class="mt-0.5 flex">
|
|
36
|
+
<div class="flex min-w-3.5 justify-end pl-0.5">
|
|
37
|
+
<div class="h-full w-px rounded-full bg-gray-300" />
|
|
38
|
+
</div>
|
|
39
|
+
<div class="min-w-0 flex-1">
|
|
40
|
+
<Astro.self items={item.children} nested />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</li>
|
|
45
|
+
))
|
|
46
|
+
}
|
|
47
|
+
</ul>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Callout from "./callout.astro";
|
|
2
|
+
import Card from "./card.astro";
|
|
3
|
+
import CardGrid from "./card-grid.astro";
|
|
4
|
+
import CodeBlock from "./code-block.astro";
|
|
5
|
+
import CodeGroup from "./code-group.astro";
|
|
6
|
+
import Disclosure from "./disclosure.astro";
|
|
7
|
+
import FileTree from "./file-tree.astro";
|
|
8
|
+
import Figure from "./figure.astro";
|
|
9
|
+
import Steps from "./steps.astro";
|
|
10
|
+
import Tab from "./tab.astro";
|
|
11
|
+
import Tabs from "./tabs.astro";
|
|
12
|
+
import Operation from "../reference/operation.astro";
|
|
13
|
+
|
|
14
|
+
/** Components for MDX content: custom elements, `pre` for code blocks, and API operations. */
|
|
15
|
+
const components = {
|
|
16
|
+
Callout,
|
|
17
|
+
Card,
|
|
18
|
+
CardGrid,
|
|
19
|
+
CodeGroup,
|
|
20
|
+
Disclosure,
|
|
21
|
+
FileTree,
|
|
22
|
+
Figure,
|
|
23
|
+
Operation,
|
|
24
|
+
Steps,
|
|
25
|
+
Tab,
|
|
26
|
+
Tabs,
|
|
27
|
+
pre: CodeBlock
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { components };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Wraps an ordered list; each item is one step, and its first paragraph is the step title.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<div
|
|
6
|
+
class:list={[
|
|
7
|
+
"my-2 [&>ol]:(list-none p-0 [counter-reset:step])",
|
|
8
|
+
"[&>ol>li]:(relative m-0 pl-11 [counter-increment:step]) [&>ol>li:not(:last-child)]:pb-4",
|
|
9
|
+
"[&>ol>li]:before:(absolute left-0 top-0 flex h-7 w-7 items-center justify-center rounded-lg bg-white text-sm font-medium text-gray-700 shadow-md shadow-gray-200 outline outline-1 -outline-offset-1 outline-gray-200 content-[counter(step)])",
|
|
10
|
+
"[&>ol>li]:after:(absolute bottom-0 left-3.5 top-9 w-px rounded-full bg-gray-200 content-[''])",
|
|
11
|
+
"[&>ol>li>p:first-child]:(mb-0 mt-0.5 font-semibold text-gray-900)",
|
|
12
|
+
"[&>ol>li>*+*]:mt-1 [&>ol>li>*:last-child]:mb-0"
|
|
13
|
+
]}
|
|
14
|
+
>
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderTabs } from "@andesine/pages/astro";
|
|
3
|
+
import { ContentTabs } from "./tabs";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Tab labels, added by the Andesine MDX plugin or content renderer. */
|
|
7
|
+
values: string;
|
|
8
|
+
/** Tabs with the same key share their selection. */
|
|
9
|
+
syncKey?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { values, panels } = await renderTabs(Astro.props.values, Astro.slots);
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<ContentTabs client:visible values={values} panels={panels} syncKey={Astro.props.syncKey} />
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Tabs } from "@andesine/ui/solid";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { type Component, For, Show } from "solid-js";
|
|
4
|
+
import { ScrollArea } from "../primitives/scroll-area";
|
|
5
|
+
import { CopyButton } from "./copy-button";
|
|
6
|
+
|
|
7
|
+
interface TabsProps {
|
|
8
|
+
values: string[];
|
|
9
|
+
syncKey?: string;
|
|
10
|
+
variant?: "content" | "code";
|
|
11
|
+
/** Rendered HTML of each tab panel. */
|
|
12
|
+
panels: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const triggerClasses = {
|
|
16
|
+
content:
|
|
17
|
+
"cursor-pointer rounded-md px-2 py-0.5 transition duration-200 ease-out @hover:text-gray-700 focus-visible:text-gray-700 data-[selected]:(bg-white text-gray-700 shadow-md outline outline-1 outline-gray-200)",
|
|
18
|
+
// Matches the section tabs: gradient text for the current tab, a gray line on hover.
|
|
19
|
+
code: clsx(
|
|
20
|
+
"relative flex h-full cursor-pointer items-center px-1.5 font-mono transition duration-200 ease-out",
|
|
21
|
+
"@hover:text-gray-700 focus-visible:text-gray-700",
|
|
22
|
+
"after:(absolute inset-x-1.5 bottom-0 h-px bg-gray-400 opacity-0 transition-opacity duration-200 content-[''])",
|
|
23
|
+
"@hover:after:opacity-100 focus-visible:after:opacity-100",
|
|
24
|
+
"data-[selected]:(bg-gradient-to-tr bg-[length:125%_auto] bg-clip-text text-transparent! after:hidden)"
|
|
25
|
+
)
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Tabs in a card: a segmented control for content, or a code header with a copy button. */
|
|
29
|
+
const ContentTabs: Component<TabsProps> = (props) => {
|
|
30
|
+
const variant = (): "content" | "code" => props.variant ?? "content";
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Tabs.Root
|
|
34
|
+
values={props.values}
|
|
35
|
+
syncKey={props.syncKey}
|
|
36
|
+
data-code-group={variant() === "code" || undefined}
|
|
37
|
+
class="not-prose my-2 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-[0_0_12px_0px] shadow-gray-200"
|
|
38
|
+
>
|
|
39
|
+
<div
|
|
40
|
+
class={clsx(
|
|
41
|
+
// The standard card header: 2.25rem high, with a bottom line. The line is drawn over the
|
|
42
|
+
// header, so the tabs can scroll across the full height and their indicator sits on it.
|
|
43
|
+
"relative flex h-9 items-center gap-2",
|
|
44
|
+
"after:(pointer-events-none absolute inset-x-0 bottom-0 h-px bg-gray-200 content-[''])",
|
|
45
|
+
variant() === "code" && "pl-1.5 pr-1"
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
{/* Many tabs scroll sideways, with a hover-only scrollbar on the bottom line. */}
|
|
49
|
+
<ScrollArea
|
|
50
|
+
orientation="horizontal"
|
|
51
|
+
class="z-1 min-w-0 flex-1 self-stretch"
|
|
52
|
+
contentClass="h-full"
|
|
53
|
+
scrollbarClass="h-1.5 px-0"
|
|
54
|
+
thumbClass="border-0"
|
|
55
|
+
>
|
|
56
|
+
<Tabs.List
|
|
57
|
+
class={clsx(
|
|
58
|
+
"relative flex h-full text-gray-500",
|
|
59
|
+
variant() === "code"
|
|
60
|
+
? "gap-1 text-xs"
|
|
61
|
+
: "min-w-full items-center gap-1 bg-gray-100 px-1 pb-px text-sm"
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
<For each={props.values}>
|
|
65
|
+
{(value) => (
|
|
66
|
+
<Tabs.Trigger value={value} class={clsx("shrink-0", triggerClasses[variant()])}>
|
|
67
|
+
{value}
|
|
68
|
+
</Tabs.Trigger>
|
|
69
|
+
)}
|
|
70
|
+
</For>
|
|
71
|
+
<Show when={variant() === "code"}>
|
|
72
|
+
{/* On the header's bottom line, like the section tabs indicator. */}
|
|
73
|
+
<Tabs.Indicator class="pointer-events-none bottom-0 h-0.5 w-[var(--width)] rounded-full bg-gradient-to-r [--transition-duration:200ms] [--transition-timing-function:ease-out]" />
|
|
74
|
+
</Show>
|
|
75
|
+
</Tabs.List>
|
|
76
|
+
</ScrollArea>
|
|
77
|
+
<Show when={variant() === "code"}>
|
|
78
|
+
<div class="ml-auto flex shrink-0 items-center">
|
|
79
|
+
<CopyButton />
|
|
80
|
+
</div>
|
|
81
|
+
</Show>
|
|
82
|
+
</div>
|
|
83
|
+
<For each={props.values}>
|
|
84
|
+
{(value, index) => (
|
|
85
|
+
<Tabs.Content
|
|
86
|
+
value={value}
|
|
87
|
+
class={clsx(
|
|
88
|
+
variant() === "content" &&
|
|
89
|
+
"prose max-w-none px-4 py-3 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
|
|
90
|
+
)}
|
|
91
|
+
innerHTML={props.panels[index()]}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
</For>
|
|
95
|
+
</Tabs.Root>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export { ContentTabs };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createScrollEdges } from "@andesine/ui/solid";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { Component } from "solid-js";
|
|
4
|
+
|
|
5
|
+
interface PageScrollShadowProps {
|
|
6
|
+
/** Positions the top fade below the sticky header. */
|
|
7
|
+
topClass: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const shadowClass =
|
|
11
|
+
"absolute inset-x-0 h-16 from-gray-50 to-gray-50/0 transition-opacity duration-150";
|
|
12
|
+
|
|
13
|
+
/** Fades the page content at the viewport edges while it scrolls past them. */
|
|
14
|
+
const PageScrollShadow: Component<PageScrollShadowProps> = (props) => {
|
|
15
|
+
const edges = createScrollEdges();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div aria-hidden="true" class="pointer-events-none sticky top-0 h-dvh">
|
|
19
|
+
<div
|
|
20
|
+
class={clsx(shadowClass, "bg-gradient-to-b", props.topClass, !edges.start() && "opacity-0")}
|
|
21
|
+
/>
|
|
22
|
+
<div class={clsx(shadowClass, "bottom-0 bg-gradient-to-t", !edges.end() && "opacity-0")} />
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { PageScrollShadow };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createPageScrollbar } from "@andesine/ui/solid";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { Component } from "solid-js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The overlay page scrollbar. The native one is hidden, so pages with and without overflow have
|
|
7
|
+
* the same width. Touch screens keep their native scrollbar.
|
|
8
|
+
*/
|
|
9
|
+
const PageScrollbar: Component = () => {
|
|
10
|
+
const scrollbar = createPageScrollbar();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
class={clsx(
|
|
16
|
+
"fixed inset-y-0 right-0.5 z-40 w-3 transition-opacity duration-200 media-touch:hidden",
|
|
17
|
+
scrollbar.visible() ? "opacity-100" : "opacity-0",
|
|
18
|
+
!scrollbar.thumb() && "hidden",
|
|
19
|
+
!scrollbar.interactive() && "pointer-events-none"
|
|
20
|
+
)}
|
|
21
|
+
onPointerDown={scrollbar.onTrackPointerDown}
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
{...scrollbar.thumbHandlers}
|
|
25
|
+
class="absolute inset-x-0 cursor-default rounded-lg border-2 border-solid border-transparent bg-gray-200 bg-clip-padding shadow-inner"
|
|
26
|
+
style={{
|
|
27
|
+
top: `${scrollbar.thumb()?.top ?? 0}px`,
|
|
28
|
+
height: `${scrollbar.thumb()?.height ?? 0}px`
|
|
29
|
+
}}
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { PageScrollbar };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { LinkConfig } from "@andesine/pages";
|
|
2
|
+
import type { Component } from "solid-js";
|
|
3
|
+
import { Button, IconButton } from "../primitives/button";
|
|
4
|
+
|
|
5
|
+
interface CallToActionProps {
|
|
6
|
+
link: LinkConfig;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** The main action link, after the Andesine landing page: the label, then an optional icon. */
|
|
10
|
+
const CallToAction: Component<CallToActionProps> = (props) =>
|
|
11
|
+
props.link.icon ? (
|
|
12
|
+
<IconButton
|
|
13
|
+
link={props.link.href}
|
|
14
|
+
icon={props.link.icon}
|
|
15
|
+
label={props.link.label}
|
|
16
|
+
color="primary"
|
|
17
|
+
variant="outlined"
|
|
18
|
+
size="small"
|
|
19
|
+
class="flex-row-reverse gap-1 pr-1.5"
|
|
20
|
+
iconClass="opacity-50 h-4.5 w-4.5"
|
|
21
|
+
/>
|
|
22
|
+
) : (
|
|
23
|
+
<Button link={props.link.href} color="primary" variant="outlined" size="small">
|
|
24
|
+
{props.link.label}
|
|
25
|
+
</Button>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export { CallToAction };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { PageContext } from "@andesine/pages";
|
|
3
|
+
import { IconButton } from "../primitives/button";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
page: PageContext;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { page } = Astro.props;
|
|
10
|
+
const { site } = page;
|
|
11
|
+
const updated =
|
|
12
|
+
page.updatedAt &&
|
|
13
|
+
new Intl.DateTimeFormat(site.language, { dateStyle: "long", timeZone: "UTC" }).format(
|
|
14
|
+
new Date(page.updatedAt)
|
|
15
|
+
);
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<!-- A line with one item keeps it at the start: icons go left when they wrap or have no date. -->
|
|
19
|
+
<footer class="flex min-h-8 flex-wrap items-center justify-between gap-4">
|
|
20
|
+
{
|
|
21
|
+
updated && (
|
|
22
|
+
<p class="text-sm text-gray-400">
|
|
23
|
+
Last updated <time datetime={page.updatedAt}>{updated}</time>
|
|
24
|
+
</p>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
{
|
|
28
|
+
site.socialLinks.length > 0 && (
|
|
29
|
+
<div class="flex items-center gap-1">
|
|
30
|
+
{site.socialLinks.map((link) => (
|
|
31
|
+
<IconButton
|
|
32
|
+
link={link.href}
|
|
33
|
+
icon={link.icon}
|
|
34
|
+
variant="text"
|
|
35
|
+
text="softer"
|
|
36
|
+
size="small"
|
|
37
|
+
aria-label={link.label}
|
|
38
|
+
title={link.label}
|
|
39
|
+
/>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
</footer>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BreadcrumbItem } from "@andesine/pages";
|
|
2
|
+
import { Breadcrumbs } from "@andesine/ui/solid";
|
|
3
|
+
import { type Component, Show } from "solid-js";
|
|
4
|
+
|
|
5
|
+
interface GroupLabelProps {
|
|
6
|
+
group: BreadcrumbItem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const labelClass = "bg-gradient-to-tr bg-[length:125%_auto] bg-clip-text text-transparent";
|
|
10
|
+
|
|
11
|
+
/** The direct parent group of the page. */
|
|
12
|
+
const GroupLabel: Component<GroupLabelProps> = (props) => (
|
|
13
|
+
<Breadcrumbs.Root>
|
|
14
|
+
<Breadcrumbs.List class="flex text-sm font-semibold">
|
|
15
|
+
<Breadcrumbs.Item class="min-w-0 truncate">
|
|
16
|
+
<Show
|
|
17
|
+
when={props.group.href}
|
|
18
|
+
fallback={<Breadcrumbs.Label class={labelClass}>{props.group.label}</Breadcrumbs.Label>}
|
|
19
|
+
>
|
|
20
|
+
{(href) => (
|
|
21
|
+
<Breadcrumbs.Link
|
|
22
|
+
href={href()}
|
|
23
|
+
class={`${labelClass} decoration-tertiary underline-offset-4 @hover:underline focus-visible:underline`}
|
|
24
|
+
>
|
|
25
|
+
{props.group.label}
|
|
26
|
+
</Breadcrumbs.Link>
|
|
27
|
+
)}
|
|
28
|
+
</Show>
|
|
29
|
+
</Breadcrumbs.Item>
|
|
30
|
+
</Breadcrumbs.List>
|
|
31
|
+
</Breadcrumbs.Root>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export { GroupLabel };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { PageContext } from "@andesine/pages";
|
|
3
|
+
import { IconButton } from "../primitives/button";
|
|
4
|
+
import { Logo } from "../primitives/logo";
|
|
5
|
+
import { SearchPalette } from "../search/search-palette";
|
|
6
|
+
import { CallToAction } from "./call-to-action";
|
|
7
|
+
import { SectionTabs } from "./section-tabs";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
page: PageContext;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { page } = Astro.props;
|
|
14
|
+
const { site } = page;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<header class="sticky top-0 z-30 col-span-full bg-gray-50">
|
|
18
|
+
<div
|
|
19
|
+
class="grid h-16 grid-cols-[minmax(0,1fr)_auto] items-center gap-4 px-4 md:(grid-cols-[17rem_minmax(0,1fr)_auto] gap-0 px-0) xl:grid-cols-[17rem_minmax(0,1fr)_17rem]"
|
|
20
|
+
>
|
|
21
|
+
<a
|
|
22
|
+
href={site.href}
|
|
23
|
+
class="min-w-0 rounded-lg text-gray-900 focus-visible:(outline outline-2 outline-offset-2 outline-tertiary) md:px-4"
|
|
24
|
+
>
|
|
25
|
+
<Logo site={site} />
|
|
26
|
+
</a>
|
|
27
|
+
<div class="hidden md:(block w-full max-w-screen-md justify-self-center px-8)">
|
|
28
|
+
{site.search && <SearchPalette client:idle search={site.search} />}
|
|
29
|
+
</div>
|
|
30
|
+
<div class="flex min-w-0 items-center justify-end gap-2 md:px-4">
|
|
31
|
+
{
|
|
32
|
+
site.search && (
|
|
33
|
+
<IconButton
|
|
34
|
+
data-search-trigger
|
|
35
|
+
icon="i-tabler:search"
|
|
36
|
+
color="contrast"
|
|
37
|
+
variant="outlined"
|
|
38
|
+
size="small"
|
|
39
|
+
text="softer"
|
|
40
|
+
class="md:hidden"
|
|
41
|
+
aria-label="Search"
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
{site.cta && <CallToAction link={site.cta} />}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
{
|
|
49
|
+
page.sections.length > 0 && site.sectionsDisplay === "tabs" && (
|
|
50
|
+
<SectionTabs client:load sections={page.sections} />
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
</header>
|