livemark 0.2.0 → 0.7.1
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/LICENSE.md +9 -0
- package/README.md +5 -1
- package/build/actions/changelog/build.d.ts +14 -0
- package/build/actions/changelog/build.js +158 -0
- package/build/actions/changelog/build.unit.js +66 -0
- package/build/actions/config/define.d.ts +6 -0
- package/build/actions/config/define.js +8 -0
- package/build/actions/config/define.unit.js +29 -0
- package/build/actions/config/load.d.ts +5 -0
- package/build/actions/config/load.js +25 -0
- package/build/actions/config/load.unit.js +23 -0
- package/build/commands/build.d.ts +5 -0
- package/build/commands/build.js +14 -0
- package/build/commands/escape.d.ts +9 -0
- package/build/commands/escape.js +56 -0
- package/build/commands/preview.d.ts +5 -0
- package/build/commands/preview.js +18 -0
- package/build/commands/start.d.ts +5 -0
- package/build/commands/start.js +19 -0
- package/build/docs/markdown/includes/example.d.ts +58 -0
- package/build/docs/markdown/includes/example.js +5 -0
- package/build/entrypoints/main.d.ts +2 -0
- package/build/entrypoints/main.js +12 -0
- package/build/helpers/program.d.ts +2 -0
- package/build/helpers/program.js +5 -0
- package/build/index.d.ts +1 -4
- package/build/index.js +2 -5
- package/build/livemark.config.d.ts +58 -0
- package/build/livemark.config.js +25 -0
- package/build/models/config.d.ts +340 -0
- package/build/models/config.js +34 -0
- package/build/models/link.d.ts +13 -0
- package/build/models/link.js +9 -0
- package/build/models/patch.d.ts +21 -0
- package/build/models/patch.js +7 -0
- package/build/models/section.d.ts +75 -0
- package/build/models/section.js +26 -0
- package/build/models/theme.d.ts +72 -0
- package/build/models/theme.js +73 -0
- package/build/program.d.ts +2 -0
- package/build/program.js +16 -0
- package/build/vite.config.d.ts +2 -0
- package/build/vite.config.js +48 -0
- package/build/website/assets/logo.svg +117 -0
- package/build/website/client.tsx +10 -0
- package/build/website/components/Abbr.tsx +17 -0
- package/build/website/components/AnsiCode.tsx +15 -0
- package/build/website/components/Article.tsx +172 -0
- package/build/website/components/BackToTop.tsx +32 -0
- package/build/website/components/Banner.tsx +32 -0
- package/build/website/components/BlogIndex.tsx +121 -0
- package/build/website/components/BlogSidebar.tsx +162 -0
- package/build/website/components/Breadcrumbs.tsx +22 -0
- package/build/website/components/Callout.tsx +72 -0
- package/build/website/components/Card.tsx +48 -0
- package/build/website/components/Cards.tsx +8 -0
- package/build/website/components/ChangelogIndex.tsx +111 -0
- package/build/website/components/ChangelogSidebar.tsx +120 -0
- package/build/website/components/CodeBlock.tsx +135 -0
- package/build/website/components/CodeTabs.tsx +83 -0
- package/build/website/components/Column.tsx +4 -0
- package/build/website/components/Columns.tsx +12 -0
- package/build/website/components/Content.tsx +7 -0
- package/build/website/components/ContentTab.tsx +7 -0
- package/build/website/components/ContentTabs.tsx +82 -0
- package/build/website/components/DefaultCatchBoundary.tsx +54 -0
- package/build/website/components/DefinitionList.tsx +21 -0
- package/build/website/components/Details.tsx +30 -0
- package/build/website/components/FileTree.tsx +52 -0
- package/build/website/components/Footer.tsx +16 -0
- package/build/website/components/FootnoteRef.tsx +52 -0
- package/build/website/components/Heading.tsx +20 -0
- package/build/website/components/InlineBadge.tsx +9 -0
- package/build/website/components/InlineIcon.tsx +10 -0
- package/build/website/components/InlineToc.tsx +31 -0
- package/build/website/components/Layout.tsx +190 -0
- package/build/website/components/LinkButton.tsx +31 -0
- package/build/website/components/Mermaid.tsx +18 -0
- package/build/website/components/MermaidRenderer.tsx +37 -0
- package/build/website/components/MiniSidebar.tsx +49 -0
- package/build/website/components/NotFound.tsx +27 -0
- package/build/website/components/PackageTabs.tsx +74 -0
- package/build/website/components/PageToolbar.tsx +75 -0
- package/build/website/components/PrevNext.tsx +60 -0
- package/build/website/components/Search.tsx +28 -0
- package/build/website/components/SearchDialog.tsx +245 -0
- package/build/website/components/Section.tsx +24 -0
- package/build/website/components/SectionHeader.tsx +35 -0
- package/build/website/components/Sidebar.tsx +160 -0
- package/build/website/components/SidebarLinks.tsx +70 -0
- package/build/website/components/SidebarSections.tsx +71 -0
- package/build/website/components/SiteTitle.tsx +21 -0
- package/build/website/components/SortableTable.tsx +81 -0
- package/build/website/components/SoundCloud.tsx +16 -0
- package/build/website/components/TagIndex.tsx +120 -0
- package/build/website/components/Theme.tsx +39 -0
- package/build/website/components/Toc.tsx +226 -0
- package/build/website/components/YouTube.tsx +16 -0
- package/build/website/components/ZoomImage.tsx +25 -0
- package/build/website/content/article.ts +78 -0
- package/build/website/content/tag.ts +29 -0
- package/build/website/content-collections.ts +208 -0
- package/build/website/elements/accordion.tsx +78 -0
- package/build/website/elements/alert-dialog.tsx +185 -0
- package/build/website/elements/alert.tsx +74 -0
- package/build/website/elements/aspect-ratio.tsx +22 -0
- package/build/website/elements/avatar.tsx +107 -0
- package/build/website/elements/badge.tsx +51 -0
- package/build/website/elements/breadcrumb.tsx +120 -0
- package/build/website/elements/button-group.tsx +87 -0
- package/build/website/elements/button.tsx +57 -0
- package/build/website/elements/calendar.tsx +229 -0
- package/build/website/elements/card.tsx +102 -0
- package/build/website/elements/carousel.tsx +240 -0
- package/build/website/elements/chart.tsx +372 -0
- package/build/website/elements/checkbox.tsx +26 -0
- package/build/website/elements/collapsible.tsx +19 -0
- package/build/website/elements/command.tsx +210 -0
- package/build/website/elements/context-menu.tsx +270 -0
- package/build/website/elements/dialog.tsx +152 -0
- package/build/website/elements/direction.tsx +4 -0
- package/build/website/elements/drawer.tsx +127 -0
- package/build/website/elements/dropdown-menu.tsx +260 -0
- package/build/website/elements/empty.tsx +104 -0
- package/build/website/elements/field.tsx +235 -0
- package/build/website/elements/hover-card.tsx +49 -0
- package/build/website/elements/input-group.tsx +171 -0
- package/build/website/elements/input-otp.tsx +84 -0
- package/build/website/elements/input.tsx +18 -0
- package/build/website/elements/item.tsx +199 -0
- package/build/website/elements/kbd.tsx +26 -0
- package/build/website/elements/label.tsx +17 -0
- package/build/website/elements/menubar.tsx +282 -0
- package/build/website/elements/native-select.tsx +56 -0
- package/build/website/elements/navigation-menu.tsx +171 -0
- package/build/website/elements/pagination.tsx +133 -0
- package/build/website/elements/popover.tsx +88 -0
- package/build/website/elements/progress.tsx +81 -0
- package/build/website/elements/radio-group.tsx +36 -0
- package/build/website/elements/resizable.tsx +48 -0
- package/build/website/elements/scroll-area.tsx +51 -0
- package/build/website/elements/select.tsx +200 -0
- package/build/website/elements/separator.tsx +22 -0
- package/build/website/elements/sheet.tsx +130 -0
- package/build/website/elements/sidebar.tsx +717 -0
- package/build/website/elements/skeleton.tsx +13 -0
- package/build/website/elements/slider.tsx +57 -0
- package/build/website/elements/sonner.tsx +40 -0
- package/build/website/elements/spinner.tsx +16 -0
- package/build/website/elements/switch.tsx +30 -0
- package/build/website/elements/table.tsx +112 -0
- package/build/website/elements/tabs.tsx +79 -0
- package/build/website/elements/textarea.tsx +16 -0
- package/build/website/elements/toggle-group.tsx +87 -0
- package/build/website/elements/toggle.tsx +43 -0
- package/build/website/elements/tooltip.tsx +67 -0
- package/build/website/env.d.ts +14 -0
- package/build/website/helpers/article-icon.ts +61 -0
- package/build/website/helpers/article.ts +90 -0
- package/build/website/helpers/changelog-source.ts +16 -0
- package/build/website/helpers/changelog-source.unit.ts +38 -0
- package/build/website/helpers/dynamic-icon-resolver.tsx +19 -0
- package/build/website/helpers/dynamic-icon.tsx +12 -0
- package/build/website/helpers/favicon.ts +7 -0
- package/build/website/helpers/model.ts +4 -0
- package/build/website/helpers/resolve-asset-path.ts +9 -0
- package/build/website/helpers/resolve-includes.ts +131 -0
- package/build/website/helpers/resolve-includes.unit.ts +126 -0
- package/build/website/helpers/search-text.ts +15 -0
- package/build/website/helpers/section.ts +50 -0
- package/build/website/helpers/section.unit.ts +71 -0
- package/build/website/helpers/style.ts +6 -0
- package/build/website/helpers/theme.ts +27 -0
- package/build/website/helpers/toc-context.ts +10 -0
- package/build/website/helpers/toc.ts +46 -0
- package/build/website/helpers/toc.unit.ts +74 -0
- package/build/website/hooks/in-view.ts +30 -0
- package/build/website/hooks/media.ts +19 -0
- package/build/website/models/article.ts +55 -0
- package/build/website/models/toc.ts +5 -0
- package/build/website/plugins/rehype-inline-code.ts +107 -0
- package/build/website/plugins/rehype-task-list-a11y.ts +33 -0
- package/build/website/plugins/remark-abbr.ts +46 -0
- package/build/website/plugins/remark-abbr.unit.ts +99 -0
- package/build/website/plugins/remark-ansi.ts +248 -0
- package/build/website/plugins/remark-ansi.unit.ts +197 -0
- package/build/website/plugins/remark-badge.ts +50 -0
- package/build/website/plugins/remark-badge.unit.ts +96 -0
- package/build/website/plugins/remark-button.ts +60 -0
- package/build/website/plugins/remark-button.unit.ts +147 -0
- package/build/website/plugins/remark-callout.ts +57 -0
- package/build/website/plugins/remark-card.ts +80 -0
- package/build/website/plugins/remark-code-tabs.ts +87 -0
- package/build/website/plugins/remark-columns.ts +66 -0
- package/build/website/plugins/remark-custom-heading-id.ts +70 -0
- package/build/website/plugins/remark-custom-heading-id.unit.ts +131 -0
- package/build/website/plugins/remark-deflist.ts +36 -0
- package/build/website/plugins/remark-details.ts +49 -0
- package/build/website/plugins/remark-filetree.ts +89 -0
- package/build/website/plugins/remark-filetree.unit.ts +137 -0
- package/build/website/plugins/remark-github-callout.ts +71 -0
- package/build/website/plugins/remark-icon.ts +42 -0
- package/build/website/plugins/remark-image.ts +34 -0
- package/build/website/plugins/remark-media.ts +62 -0
- package/build/website/plugins/remark-media.unit.ts +74 -0
- package/build/website/plugins/remark-mermaid.ts +23 -0
- package/build/website/plugins/remark-npm.ts +52 -0
- package/build/website/plugins/remark-steps.ts +36 -0
- package/build/website/plugins/remark-steps.unit.ts +97 -0
- package/build/website/plugins/remark-tab.ts +100 -0
- package/build/website/plugins/remark-tab.unit.ts +181 -0
- package/build/website/plugins/remark-toc.ts +39 -0
- package/build/website/plugins/remark-toc.unit.ts +82 -0
- package/build/website/plugins/shiki-icon.ts +82 -0
- package/build/website/plugins/shiki-line-highlight.ts +174 -0
- package/build/website/plugins/vite-livemark.ts +91 -0
- package/build/website/routeTree.gen.ts +86 -0
- package/build/website/router.tsx +34 -0
- package/build/website/routes/$.tsx +92 -0
- package/build/website/routes/__root.tsx +97 -0
- package/build/website/server.ts +7 -0
- package/build/website/styles/general.css +163 -0
- package/build/website/styles/markdown.css +214 -0
- package/build/website/vite.config.ts +71 -0
- package/package.json +120 -12
- package/skills/authoring/SKILL.md +97 -0
- package/skills/configuration/SKILL.md +120 -0
- package/skills/customization/SKILL.md +118 -0
- package/build/descriptor/index.d.ts +0 -3
- package/build/descriptor/index.js +0 -4
- package/build/descriptor/load.d.ts +0 -8
- package/build/descriptor/load.js +0 -40
- package/build/descriptor/node.d.ts +0 -125
- package/build/descriptor/node.js +0 -9
- package/build/descriptor/save.d.ts +0 -8
- package/build/descriptor/save.js +0 -17
- package/build/descriptor/save.spec.js +0 -80
- package/build/descriptor/validate.d.ts +0 -9
- package/build/descriptor/validate.js +0 -14
- package/build/descriptor/validate.spec.js +0 -124
- package/build/dialect/Dialect.d.ts +0 -88
- package/build/dialect/Dialect.js +0 -2
- package/build/dialect/index.d.ts +0 -3
- package/build/dialect/index.js +0 -3
- package/build/dialect/load.d.ts +0 -4
- package/build/dialect/load.js +0 -7
- package/build/dialect/save.d.ts +0 -9
- package/build/dialect/save.js +0 -13
- package/build/dialect/validate.d.ts +0 -1
- package/build/dialect/validate.js +0 -4
- package/build/metadata/index.d.ts +0 -3
- package/build/metadata/index.js +0 -4
- package/build/metadata/load.d.ts +0 -10
- package/build/metadata/load.js +0 -55
- package/build/metadata/node.d.ts +0 -125
- package/build/metadata/node.js +0 -9
- package/build/metadata/save.d.ts +0 -8
- package/build/metadata/save.js +0 -17
- package/build/metadata/save.spec.js +0 -80
- package/build/metadata/validate.d.ts +0 -9
- package/build/metadata/validate.js +0 -14
- package/build/metadata/validate.spec.d.ts +0 -1
- package/build/metadata/validate.spec.js +0 -124
- package/build/package/Contributor.d.ts +0 -21
- package/build/package/Contributor.js +0 -2
- package/build/package/Package.d.ts +0 -65
- package/build/package/Package.js +0 -2
- package/build/package/index.d.ts +0 -4
- package/build/package/index.js +0 -3
- package/build/package/load.d.ts +0 -4
- package/build/package/load.js +0 -7
- package/build/package/save.d.ts +0 -9
- package/build/package/save.js +0 -13
- package/build/package/validate.d.ts +0 -1
- package/build/package/validate.js +0 -4
- package/build/resource/License.d.ts +0 -18
- package/build/resource/License.js +0 -2
- package/build/resource/Resource.d.ts +0 -67
- package/build/resource/Resource.js +0 -2
- package/build/resource/Source.d.ts +0 -17
- package/build/resource/Source.js +0 -2
- package/build/resource/index.d.ts +0 -5
- package/build/resource/index.js +0 -2
- package/build/resource/load.d.ts +0 -1
- package/build/resource/load.js +0 -2
- package/build/resource/save.d.ts +0 -9
- package/build/resource/save.js +0 -13
- package/build/resource/types/index.d.ts +0 -1
- package/build/resource/types/index.js +0 -2
- package/build/resource/types/table.d.ts +0 -29
- package/build/resource/types/table.js +0 -2
- package/build/resource/validate.d.ts +0 -1
- package/build/resource/validate.js +0 -4
- package/build/schema/Field.d.ts +0 -5
- package/build/schema/Field.js +0 -2
- package/build/schema/Schema.d.ts +0 -55
- package/build/schema/Schema.js +0 -2
- package/build/schema/fields/any.d.ts +0 -20
- package/build/schema/fields/any.js +0 -2
- package/build/schema/fields/array.d.ts +0 -32
- package/build/schema/fields/array.js +0 -2
- package/build/schema/fields/base.d.ts +0 -42
- package/build/schema/fields/base.js +0 -2
- package/build/schema/fields/boolean.d.ts +0 -28
- package/build/schema/fields/boolean.js +0 -2
- package/build/schema/fields/date.d.ts +0 -35
- package/build/schema/fields/date.js +0 -2
- package/build/schema/fields/datetime.d.ts +0 -35
- package/build/schema/fields/datetime.js +0 -2
- package/build/schema/fields/duration.d.ts +0 -28
- package/build/schema/fields/duration.js +0 -2
- package/build/schema/fields/geojson.d.ts +0 -26
- package/build/schema/fields/geojson.js +0 -2
- package/build/schema/fields/geopoint.d.ts +0 -27
- package/build/schema/fields/geopoint.js +0 -2
- package/build/schema/fields/index.d.ts +0 -16
- package/build/schema/fields/index.js +0 -17
- package/build/schema/fields/integer.d.ts +0 -37
- package/build/schema/fields/integer.js +0 -2
- package/build/schema/fields/list.d.ts +0 -36
- package/build/schema/fields/list.js +0 -2
- package/build/schema/fields/number.d.ts +0 -40
- package/build/schema/fields/number.js +0 -2
- package/build/schema/fields/object.d.ts +0 -32
- package/build/schema/fields/object.js +0 -2
- package/build/schema/fields/string.d.ts +0 -40
- package/build/schema/fields/string.js +0 -2
- package/build/schema/fields/time.d.ts +0 -35
- package/build/schema/fields/time.js +0 -2
- package/build/schema/fields/year.d.ts +0 -28
- package/build/schema/fields/year.js +0 -2
- package/build/schema/fields/yearmonth.d.ts +0 -28
- package/build/schema/fields/yearmonth.js +0 -2
- package/build/schema/index.d.ts +0 -4
- package/build/schema/index.js +0 -2
- package/build/schema/load.d.ts +0 -1
- package/build/schema/load.js +0 -2
- package/build/schema/save.d.ts +0 -9
- package/build/schema/save.js +0 -13
- package/build/schema/validate.d.ts +0 -1
- package/build/schema/validate.js +0 -4
- package/build/tsconfig.tsbuildinfo +0 -1
- package/index.ts +0 -0
- package/tsconfig.json +0 -10
- package/typedoc.json +0 -4
- /package/build/{descriptor/save.spec.d.ts → actions/changelog/build.unit.d.ts} +0 -0
- /package/build/{descriptor/validate.spec.d.ts → actions/config/define.unit.d.ts} +0 -0
- /package/build/{metadata/save.spec.d.ts → actions/config/load.unit.d.ts} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { File, Folder } from "lucide-react"
|
|
2
|
+
|
|
3
|
+
interface TreeItem {
|
|
4
|
+
name: string
|
|
5
|
+
isDir: boolean
|
|
6
|
+
children?: TreeItem[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Renders a file/directory tree from a JSON-serialized tree structure */
|
|
10
|
+
export function FileTree(props: { tree: string }) {
|
|
11
|
+
const items: TreeItem[] = JSON.parse(props.tree)
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="not-prose my-4 rounded-xl border border-border bg-card p-4 font-mono text-sm">
|
|
15
|
+
<TreeItems items={items} depth={0} />
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function TreeItems(props: { items: TreeItem[]; depth: number }) {
|
|
21
|
+
return (
|
|
22
|
+
<ul className={props.depth > 0 ? "ml-4 border-l border-border pl-2" : ""}>
|
|
23
|
+
{props.items.map((item, index) => (
|
|
24
|
+
<TreeNode
|
|
25
|
+
key={`${item.name}-${index}`}
|
|
26
|
+
item={item}
|
|
27
|
+
depth={props.depth}
|
|
28
|
+
/>
|
|
29
|
+
))}
|
|
30
|
+
</ul>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function TreeNode(props: { item: TreeItem; depth: number }) {
|
|
35
|
+
const { item, depth } = props
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<li className="py-0.5">
|
|
39
|
+
<span className="inline-flex items-center gap-2">
|
|
40
|
+
{item.isDir ? (
|
|
41
|
+
<Folder className="size-4 shrink-0 text-primary" />
|
|
42
|
+
) : (
|
|
43
|
+
<File className="size-4 shrink-0 text-muted-foreground" />
|
|
44
|
+
)}
|
|
45
|
+
<span className={item.isDir ? "font-semibold" : ""}>{item.name}</span>
|
|
46
|
+
</span>
|
|
47
|
+
{item.children && item.children.length > 0 && (
|
|
48
|
+
<TreeItems items={item.children} depth={depth + 1} />
|
|
49
|
+
)}
|
|
50
|
+
</li>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Footer shown below article content */
|
|
2
|
+
export function Footer() {
|
|
3
|
+
return (
|
|
4
|
+
<p className="mt-12 text-center text-sm text-muted-foreground">
|
|
5
|
+
Created with <span className="text-red-500">❤</span> and{" "}
|
|
6
|
+
<a
|
|
7
|
+
href="https://github.com/datisthq/livemark"
|
|
8
|
+
target="_blank"
|
|
9
|
+
rel="noopener noreferrer"
|
|
10
|
+
className="font-medium hover:text-foreground transition-colors"
|
|
11
|
+
>
|
|
12
|
+
Livemark
|
|
13
|
+
</a>
|
|
14
|
+
</p>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ComponentProps, useEffect, useState } from "react"
|
|
2
|
+
import {
|
|
3
|
+
HoverCard,
|
|
4
|
+
HoverCardContent,
|
|
5
|
+
HoverCardTrigger,
|
|
6
|
+
} from "../elements/hover-card.tsx"
|
|
7
|
+
|
|
8
|
+
const FOOTNOTE_PREFIX = "#user-content-fn-"
|
|
9
|
+
|
|
10
|
+
/** Wraps footnote reference links in a HoverCard that previews footnote content inline */
|
|
11
|
+
export function FootnoteRef(props: ComponentProps<"a">) {
|
|
12
|
+
const href = props.href ?? ""
|
|
13
|
+
if (!href.startsWith(FOOTNOTE_PREFIX)) {
|
|
14
|
+
return <a {...props} />
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return <FootnoteHoverCard {...props} href={href} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function FootnoteHoverCard(props: ComponentProps<"a"> & { href: string }) {
|
|
21
|
+
const [footnoteHtml, setFootnoteHtml] = useState("")
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const targetId = props.href.slice(1)
|
|
25
|
+
const el = document.getElementById(targetId)
|
|
26
|
+
if (!el) return
|
|
27
|
+
|
|
28
|
+
const wrapper = document.createElement("div")
|
|
29
|
+
wrapper.innerHTML = el.innerHTML
|
|
30
|
+
for (const backref of wrapper.querySelectorAll("[data-footnote-backref]")) {
|
|
31
|
+
backref.remove()
|
|
32
|
+
}
|
|
33
|
+
setFootnoteHtml(wrapper.innerHTML)
|
|
34
|
+
}, [props.href])
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<HoverCard>
|
|
38
|
+
<HoverCardTrigger render={<a {...props} />} />
|
|
39
|
+
<HoverCardContent
|
|
40
|
+
className="prose dark:prose-invert text-sm w-80 [&>p]:my-1 [&>p:first-child]:mt-0 [&>p:last-child]:mb-0"
|
|
41
|
+
side="top"
|
|
42
|
+
sideOffset={8}
|
|
43
|
+
>
|
|
44
|
+
{footnoteHtml ? (
|
|
45
|
+
<div dangerouslySetInnerHTML={{ __html: footnoteHtml }} />
|
|
46
|
+
) : (
|
|
47
|
+
<p className="text-muted-foreground">Footnote not found</p>
|
|
48
|
+
)}
|
|
49
|
+
</HoverCardContent>
|
|
50
|
+
</HoverCard>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Heading component that wraps content in an anchor link to itself */
|
|
2
|
+
function createHeading(Tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6") {
|
|
3
|
+
return function Heading(props: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
4
|
+
if (!props.id) return <Tag {...props} />
|
|
5
|
+
return (
|
|
6
|
+
<Tag {...props}>
|
|
7
|
+
<a href={`#${props.id}`}>{props.children}</a>
|
|
8
|
+
</Tag>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const headingComponents = {
|
|
14
|
+
h1: createHeading("h1"),
|
|
15
|
+
h2: createHeading("h2"),
|
|
16
|
+
h3: createHeading("h3"),
|
|
17
|
+
h4: createHeading("h4"),
|
|
18
|
+
h5: createHeading("h5"),
|
|
19
|
+
h6: createHeading("h6"),
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Badge } from "../elements/badge.tsx"
|
|
2
|
+
|
|
3
|
+
/** Inline badge rendered from :badge text directives */
|
|
4
|
+
export function InlineBadge(props: {
|
|
5
|
+
label: string
|
|
6
|
+
variant?: "default" | "secondary" | "destructive" | "outline"
|
|
7
|
+
}) {
|
|
8
|
+
return <Badge variant={props.variant}>{props.label}</Badge>
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DynamicIcon } from "../helpers/dynamic-icon.tsx"
|
|
2
|
+
|
|
3
|
+
/** Renders a Lucide icon inline by name */
|
|
4
|
+
export function InlineIcon(props: { name: string; className?: string }) {
|
|
5
|
+
const className = props.className
|
|
6
|
+
? `inline-block size-4 align-text-bottom ${props.className}`
|
|
7
|
+
: "inline-block size-4 align-text-bottom"
|
|
8
|
+
|
|
9
|
+
return <DynamicIcon name={props.name} className={className} />
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useTocItems } from "../helpers/toc-context.ts"
|
|
2
|
+
|
|
3
|
+
/** Renders the page's table of contents inline within the content */
|
|
4
|
+
export function InlineToc(props: { maxLevel?: string }) {
|
|
5
|
+
const allItems = useTocItems()
|
|
6
|
+
const maxDepth = props.maxLevel ? Number(props.maxLevel) : 4
|
|
7
|
+
const items = allItems.filter(item => item.depth <= maxDepth)
|
|
8
|
+
|
|
9
|
+
if (items.length === 0) return null
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<nav className="not-prose my-8 rounded-xl border border-border bg-card p-6">
|
|
13
|
+
<p className="text-base font-semibold mb-4">Table of Contents</p>
|
|
14
|
+
<ul className="text-base space-y-2.5">
|
|
15
|
+
{items.map(item => (
|
|
16
|
+
<li
|
|
17
|
+
key={item.url}
|
|
18
|
+
style={{ paddingLeft: `${(item.depth - 2) * 1.25}rem` }}
|
|
19
|
+
>
|
|
20
|
+
<a
|
|
21
|
+
href={item.url}
|
|
22
|
+
className="text-muted-foreground hover:text-primary transition-colors"
|
|
23
|
+
>
|
|
24
|
+
{item.title}
|
|
25
|
+
</a>
|
|
26
|
+
</li>
|
|
27
|
+
))}
|
|
28
|
+
</ul>
|
|
29
|
+
</nav>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Link, useLocation, useMatch } from "@tanstack/react-router"
|
|
2
|
+
import { useHotkey } from "@tanstack/react-hotkeys"
|
|
3
|
+
import { ExternalLink } from "lucide-react"
|
|
4
|
+
import {
|
|
5
|
+
currentSection,
|
|
6
|
+
sectionFirstArticle,
|
|
7
|
+
sectionFlatArticles,
|
|
8
|
+
sortedArticles,
|
|
9
|
+
} from "../content/article.ts"
|
|
10
|
+
import { DynamicIcon } from "../helpers/dynamic-icon.tsx"
|
|
11
|
+
import { sectionIcon } from "../helpers/section.ts"
|
|
12
|
+
import {
|
|
13
|
+
SidebarInset,
|
|
14
|
+
SidebarProvider,
|
|
15
|
+
SidebarTrigger,
|
|
16
|
+
useSidebar,
|
|
17
|
+
} from "../elements/sidebar.tsx"
|
|
18
|
+
import { BackToTop } from "./BackToTop.tsx"
|
|
19
|
+
import { Banner } from "./Banner.tsx"
|
|
20
|
+
import { BlogSidebar } from "./BlogSidebar.tsx"
|
|
21
|
+
import { ChangelogSidebar } from "./ChangelogSidebar.tsx"
|
|
22
|
+
import { MiniSidebar } from "./MiniSidebar.tsx"
|
|
23
|
+
import { Sidebar } from "./Sidebar.tsx"
|
|
24
|
+
import { SiteTitle } from "./SiteTitle.tsx"
|
|
25
|
+
|
|
26
|
+
function splatFor(pathname: string) {
|
|
27
|
+
return { _splat: pathname.replace(/^\/|\/$/g, "") }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function Layout(props: {
|
|
31
|
+
withSidebar?: boolean
|
|
32
|
+
children?: React.ReactNode
|
|
33
|
+
}) {
|
|
34
|
+
const pathname = useLocation({ select: l => l.pathname })
|
|
35
|
+
const articleRoute = useMatch({ from: "/$", shouldThrow: false })
|
|
36
|
+
const activeSection = articleRoute
|
|
37
|
+
? currentSection(`/${pathname.replace(/^\/|\/$/g, "")}/`)
|
|
38
|
+
: undefined
|
|
39
|
+
|
|
40
|
+
useHotkey("J", () =>
|
|
41
|
+
window.scrollBy({ top: window.innerHeight * 0.8, behavior: "smooth" }),
|
|
42
|
+
)
|
|
43
|
+
useHotkey("K", () =>
|
|
44
|
+
window.scrollBy({ top: -window.innerHeight * 0.8, behavior: "smooth" }),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const sections = import.meta.env.CONFIG.sections
|
|
48
|
+
|
|
49
|
+
const header = (
|
|
50
|
+
<header className="sticky top-0 z-20 flex h-16 shrink-0 items-center border-b bg-background">
|
|
51
|
+
<div
|
|
52
|
+
className={`flex items-center self-stretch pl-4 ${props.withSidebar ? "pr-4" : "md:pr-20"}`}
|
|
53
|
+
>
|
|
54
|
+
{props.withSidebar ? (
|
|
55
|
+
<>
|
|
56
|
+
<div className="hidden md:flex">
|
|
57
|
+
<SidebarTrigger />
|
|
58
|
+
</div>
|
|
59
|
+
<Link to="/" className="md:hidden flex items-center gap-2">
|
|
60
|
+
<SiteTitle />
|
|
61
|
+
</Link>
|
|
62
|
+
</>
|
|
63
|
+
) : (
|
|
64
|
+
<Link to="/" className="flex items-center gap-2">
|
|
65
|
+
<SiteTitle />
|
|
66
|
+
</Link>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
<div className="hidden md:flex flex-1 items-center gap-8 self-stretch px-6 text-sm">
|
|
70
|
+
{sections?.length ? (
|
|
71
|
+
sections
|
|
72
|
+
.filter(section => section.position === "header")
|
|
73
|
+
.map(section => {
|
|
74
|
+
const isActive = activeSection?.prefix === section.prefix
|
|
75
|
+
const target =
|
|
76
|
+
section.type === "blog" || section.type === "changelog"
|
|
77
|
+
? section.prefix
|
|
78
|
+
: sectionFirstArticle.get(section.prefix)
|
|
79
|
+
const latestVersion =
|
|
80
|
+
section.type === "changelog" && section.version
|
|
81
|
+
? sortedArticles.find(
|
|
82
|
+
a =>
|
|
83
|
+
a.path === sectionFlatArticles.get(section.prefix)?.[0],
|
|
84
|
+
)?.title
|
|
85
|
+
: undefined
|
|
86
|
+
return (
|
|
87
|
+
<Link
|
|
88
|
+
key={section.prefix}
|
|
89
|
+
to={target === "/" ? "/" : "/$"}
|
|
90
|
+
params={target && target !== "/" ? splatFor(target) : {}}
|
|
91
|
+
className={`inline-flex items-center gap-1.5 text-foreground ${
|
|
92
|
+
isActive
|
|
93
|
+
? "font-medium border-b-2 border-foreground pb-0.5 -mb-0.5"
|
|
94
|
+
: "opacity-80 hover:opacity-100 transition-opacity"
|
|
95
|
+
}`}
|
|
96
|
+
>
|
|
97
|
+
<DynamicIcon
|
|
98
|
+
name={sectionIcon(section)}
|
|
99
|
+
className="size-3.5"
|
|
100
|
+
/>
|
|
101
|
+
<span>
|
|
102
|
+
{section.title}
|
|
103
|
+
{latestVersion && (
|
|
104
|
+
<span className="opacity-80"> ({latestVersion})</span>
|
|
105
|
+
)}
|
|
106
|
+
</span>
|
|
107
|
+
</Link>
|
|
108
|
+
)
|
|
109
|
+
})
|
|
110
|
+
) : (
|
|
111
|
+
<Link
|
|
112
|
+
to="/"
|
|
113
|
+
className="text-foreground font-medium underline underline-offset-4"
|
|
114
|
+
>
|
|
115
|
+
Docs
|
|
116
|
+
</Link>
|
|
117
|
+
)}
|
|
118
|
+
{import.meta.env.CONFIG.links
|
|
119
|
+
?.filter(
|
|
120
|
+
link =>
|
|
121
|
+
link.position === "header" &&
|
|
122
|
+
(!link.prefix || link.prefix === activeSection?.prefix),
|
|
123
|
+
)
|
|
124
|
+
.map(link => (
|
|
125
|
+
<a
|
|
126
|
+
key={link.url}
|
|
127
|
+
href={link.url}
|
|
128
|
+
target="_blank"
|
|
129
|
+
rel="noopener noreferrer"
|
|
130
|
+
className="text-foreground opacity-80 hover:opacity-100 transition-opacity"
|
|
131
|
+
>
|
|
132
|
+
{link.icon && (
|
|
133
|
+
<DynamicIcon
|
|
134
|
+
name={link.icon}
|
|
135
|
+
className="inline size-3.5 align-[-0.125em]"
|
|
136
|
+
/>
|
|
137
|
+
)}{" "}
|
|
138
|
+
{link.title}{" "}
|
|
139
|
+
<ExternalLink className="inline size-3 align-[-0.125em]" />
|
|
140
|
+
</a>
|
|
141
|
+
))}
|
|
142
|
+
<Banner />
|
|
143
|
+
</div>
|
|
144
|
+
<div className="md:hidden ml-auto pr-2">
|
|
145
|
+
<SidebarTrigger />
|
|
146
|
+
</div>
|
|
147
|
+
</header>
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const SidebarComponent = !props.withSidebar
|
|
151
|
+
? MiniSidebar
|
|
152
|
+
: activeSection?.type === "blog"
|
|
153
|
+
? BlogSidebar
|
|
154
|
+
: activeSection?.type === "changelog"
|
|
155
|
+
? ChangelogSidebar
|
|
156
|
+
: Sidebar
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<SidebarProvider
|
|
160
|
+
key={props.withSidebar ? "sidebar" : "mini"}
|
|
161
|
+
defaultOpen={!!props.withSidebar}
|
|
162
|
+
>
|
|
163
|
+
<SidebarShell SidebarComponent={SidebarComponent} header={header}>
|
|
164
|
+
{props.children}
|
|
165
|
+
</SidebarShell>
|
|
166
|
+
</SidebarProvider>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function SidebarShell(props: {
|
|
171
|
+
SidebarComponent: React.ComponentType
|
|
172
|
+
header: React.ReactNode
|
|
173
|
+
children?: React.ReactNode
|
|
174
|
+
}) {
|
|
175
|
+
const { isMobile, setOpenMobile } = useSidebar()
|
|
176
|
+
const onClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
177
|
+
if (!isMobile) return
|
|
178
|
+
if ((event.target as HTMLElement).closest("a")) setOpenMobile(false)
|
|
179
|
+
}
|
|
180
|
+
return (
|
|
181
|
+
<div style={{ display: "contents" }} onClick={onClick}>
|
|
182
|
+
<props.SidebarComponent />
|
|
183
|
+
<SidebarInset>
|
|
184
|
+
{props.header}
|
|
185
|
+
<main className="flex-1">{props.children}</main>
|
|
186
|
+
<BackToTop />
|
|
187
|
+
</SidebarInset>
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { buttonVariants } from "../elements/button.tsx"
|
|
2
|
+
import { cn } from "../helpers/style.ts"
|
|
3
|
+
|
|
4
|
+
/** Styled link button rendered from ::button leaf directives */
|
|
5
|
+
export function LinkButton(props: {
|
|
6
|
+
href: string
|
|
7
|
+
label: string
|
|
8
|
+
variant?:
|
|
9
|
+
| "default"
|
|
10
|
+
| "outline"
|
|
11
|
+
| "secondary"
|
|
12
|
+
| "ghost"
|
|
13
|
+
| "destructive"
|
|
14
|
+
| "link"
|
|
15
|
+
size?: "default" | "sm" | "lg"
|
|
16
|
+
}) {
|
|
17
|
+
return (
|
|
18
|
+
<a
|
|
19
|
+
href={props.href}
|
|
20
|
+
className={cn(
|
|
21
|
+
buttonVariants({
|
|
22
|
+
variant: props.variant,
|
|
23
|
+
size: props.size,
|
|
24
|
+
}),
|
|
25
|
+
"no-underline",
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
{props.label}
|
|
29
|
+
</a>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Suspense, lazy } from "react"
|
|
2
|
+
|
|
3
|
+
const MermaidRenderer = lazy(() => import("./MermaidRenderer.tsx"))
|
|
4
|
+
|
|
5
|
+
/** Lazily loads and renders a Mermaid diagram */
|
|
6
|
+
export function Mermaid(props: { children: string }) {
|
|
7
|
+
return (
|
|
8
|
+
<Suspense
|
|
9
|
+
fallback={
|
|
10
|
+
<div className="not-prose mermaid flex justify-center">
|
|
11
|
+
Loading diagram…
|
|
12
|
+
</div>
|
|
13
|
+
}
|
|
14
|
+
>
|
|
15
|
+
<MermaidRenderer>{props.children}</MermaidRenderer>
|
|
16
|
+
</Suspense>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react"
|
|
2
|
+
import mermaid from "mermaid"
|
|
3
|
+
|
|
4
|
+
mermaid.initialize({ startOnLoad: false })
|
|
5
|
+
|
|
6
|
+
/** Renders a Mermaid diagram from a chart definition string */
|
|
7
|
+
export default function MermaidRenderer(props: { children: string }) {
|
|
8
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const el = ref.current
|
|
12
|
+
if (!el) return
|
|
13
|
+
|
|
14
|
+
const render = () => {
|
|
15
|
+
const isDark = document.documentElement.classList.contains("dark")
|
|
16
|
+
mermaid.initialize({
|
|
17
|
+
startOnLoad: false,
|
|
18
|
+
theme: isDark ? "dark" : "default",
|
|
19
|
+
})
|
|
20
|
+
el.textContent = props.children
|
|
21
|
+
el.removeAttribute("data-processed")
|
|
22
|
+
mermaid.run({ nodes: [el] })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
render()
|
|
26
|
+
|
|
27
|
+
const observer = new MutationObserver(render)
|
|
28
|
+
observer.observe(document.documentElement, {
|
|
29
|
+
attributes: true,
|
|
30
|
+
attributeFilter: ["class"],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return () => observer.disconnect()
|
|
34
|
+
}, [props.children])
|
|
35
|
+
|
|
36
|
+
return <div ref={ref} className="not-prose mermaid flex justify-center" />
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Link } from "@tanstack/react-router"
|
|
2
|
+
import { useHotkey } from "@tanstack/react-hotkeys"
|
|
3
|
+
import {
|
|
4
|
+
Sidebar as SidebarRoot,
|
|
5
|
+
SidebarContent,
|
|
6
|
+
SidebarFooter,
|
|
7
|
+
SidebarHeader,
|
|
8
|
+
SidebarMenu,
|
|
9
|
+
SidebarMenuButton,
|
|
10
|
+
SidebarMenuItem,
|
|
11
|
+
SidebarRail,
|
|
12
|
+
useSidebar,
|
|
13
|
+
} from "../elements/sidebar.tsx"
|
|
14
|
+
import { Search } from "./Search.tsx"
|
|
15
|
+
import { SidebarLinks } from "./SidebarLinks.tsx"
|
|
16
|
+
import { SidebarSections } from "./SidebarSections.tsx"
|
|
17
|
+
import { SiteTitle } from "./SiteTitle.tsx"
|
|
18
|
+
import { Theme } from "./Theme.tsx"
|
|
19
|
+
|
|
20
|
+
/** Minimal sidebar used on pages without an article tree. On desktop the
|
|
21
|
+
* SidebarProvider keeps it off-canvas; on mobile it drives the sheet so
|
|
22
|
+
* users can still reach Sections / Links / Search / Theme. */
|
|
23
|
+
export function MiniSidebar() {
|
|
24
|
+
const { toggleSidebar } = useSidebar()
|
|
25
|
+
useHotkey("S", toggleSidebar)
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<SidebarRoot>
|
|
29
|
+
<SidebarHeader>
|
|
30
|
+
<SidebarMenu>
|
|
31
|
+
<SidebarMenuItem>
|
|
32
|
+
<SidebarMenuButton size="lg" render={<Link to="/" />}>
|
|
33
|
+
<SiteTitle />
|
|
34
|
+
</SidebarMenuButton>
|
|
35
|
+
</SidebarMenuItem>
|
|
36
|
+
</SidebarMenu>
|
|
37
|
+
</SidebarHeader>
|
|
38
|
+
<SidebarContent>
|
|
39
|
+
<SidebarSections />
|
|
40
|
+
<SidebarLinks />
|
|
41
|
+
</SidebarContent>
|
|
42
|
+
<SidebarFooter className="p-4 space-y-2">
|
|
43
|
+
<Search />
|
|
44
|
+
<Theme />
|
|
45
|
+
</SidebarFooter>
|
|
46
|
+
<SidebarRail />
|
|
47
|
+
</SidebarRoot>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Link } from "@tanstack/react-router"
|
|
2
|
+
|
|
3
|
+
export function NotFound({ children }: { children?: any }) {
|
|
4
|
+
return (
|
|
5
|
+
<div className="min-h-[60vh] flex flex-col items-center justify-center text-center px-4">
|
|
6
|
+
<h1 className="font-sans text-6xl font-semibold mb-4">404</h1>
|
|
7
|
+
<div className="text-muted-foreground mb-6">
|
|
8
|
+
{children || <p>The page you're looking for doesn't exist.</p>}
|
|
9
|
+
</div>
|
|
10
|
+
<div className="flex items-center gap-4">
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
onClick={() => window.history.back()}
|
|
14
|
+
className="bg-primary text-primary-foreground px-4 py-2 text-sm font-medium hover:bg-primary/80 transition-colors"
|
|
15
|
+
>
|
|
16
|
+
Go Back
|
|
17
|
+
</button>
|
|
18
|
+
<Link
|
|
19
|
+
to="/"
|
|
20
|
+
className="border border-border px-4 py-2 text-sm font-medium hover:bg-card transition-colors"
|
|
21
|
+
>
|
|
22
|
+
Home
|
|
23
|
+
</Link>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Check, Clipboard } from "lucide-react"
|
|
2
|
+
import { useState } from "react"
|
|
3
|
+
|
|
4
|
+
type Manager = "npm" | "pnpm" | "yarn" | "bun"
|
|
5
|
+
|
|
6
|
+
const managers: Manager[] = ["npm", "pnpm", "yarn", "bun"]
|
|
7
|
+
|
|
8
|
+
interface PackageTabsProps {
|
|
9
|
+
npm: string
|
|
10
|
+
pnpm: string
|
|
11
|
+
yarn: string
|
|
12
|
+
bun: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Tabbed code block showing a command for each package manager */
|
|
16
|
+
export function PackageTabs(props: PackageTabsProps) {
|
|
17
|
+
const [active, setActive] = useState<Manager>("npm")
|
|
18
|
+
const [copied, setCopied] = useState(false)
|
|
19
|
+
|
|
20
|
+
const commands: Record<Manager, string> = {
|
|
21
|
+
npm: props.npm,
|
|
22
|
+
pnpm: props.pnpm,
|
|
23
|
+
yarn: props.yarn,
|
|
24
|
+
bun: props.bun,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const handleCopy = () => {
|
|
28
|
+
navigator.clipboard.writeText(commands[active])
|
|
29
|
+
setCopied(true)
|
|
30
|
+
setTimeout(() => setCopied(false), 2000)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<figure className="not-prose my-4 relative rounded-xl border shadow-sm overflow-hidden text-sm bg-sidebar dark:bg-sidebar">
|
|
35
|
+
<div className="flex items-center border-b">
|
|
36
|
+
<div className="flex flex-1 items-center">
|
|
37
|
+
{managers.map(pm => (
|
|
38
|
+
<button
|
|
39
|
+
key={pm}
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={() => setActive(pm)}
|
|
42
|
+
className={`px-3 py-2 text-xs font-medium transition-colors ${
|
|
43
|
+
pm === active
|
|
44
|
+
? "text-foreground border-b-2 border-foreground"
|
|
45
|
+
: "text-muted-foreground hover:text-foreground"
|
|
46
|
+
}`}
|
|
47
|
+
>
|
|
48
|
+
{pm}
|
|
49
|
+
</button>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
onClick={handleCopy}
|
|
55
|
+
className="me-2 p-1 rounded-md text-muted-foreground hover:text-foreground transition-colors"
|
|
56
|
+
aria-label="Copy code"
|
|
57
|
+
>
|
|
58
|
+
{copied ? (
|
|
59
|
+
<Check className="size-3.5" />
|
|
60
|
+
) : (
|
|
61
|
+
<Clipboard className="size-3.5" />
|
|
62
|
+
)}
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="text-[0.8125rem] py-3.5 overflow-auto max-h-[600px]">
|
|
66
|
+
<pre className="min-w-full w-max">
|
|
67
|
+
<code className="flex flex-col">
|
|
68
|
+
<span className="px-4">{commands[active]}</span>
|
|
69
|
+
</code>
|
|
70
|
+
</pre>
|
|
71
|
+
</div>
|
|
72
|
+
</figure>
|
|
73
|
+
)
|
|
74
|
+
}
|