mirror of
https://github.com/dathere/ckan-devstaller.git
synced 2025-11-09 13:39:49 +00:00
feat(docs): add WIP builder and update biome
This commit is contained in:
parent
02d651177e
commit
af15fb3fed
10 changed files with 790 additions and 26 deletions
|
|
@ -1,8 +1,15 @@
|
||||||
/** biome-ignore-all lint/a11y/useButtonType: <explanation> */
|
/** biome-ignore-all lint/suspicious/noArrayIndexKey: Would need to look into this trivial issue */
|
||||||
/** biome-ignore-all lint/suspicious/noArrayIndexKey: <explanation> */
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||||
import { cn } from "fumadocs-ui/utils/cn";
|
import { cn } from "fumadocs-ui/utils/cn";
|
||||||
|
import {
|
||||||
|
BlocksIcon,
|
||||||
|
GitMergeIcon,
|
||||||
|
HomeIcon,
|
||||||
|
SailboatIcon,
|
||||||
|
ZapIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -12,6 +19,7 @@ import CkanDevstallerDemo from "./ckan-devstaller-demo.gif";
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const gridColor =
|
const gridColor =
|
||||||
"color-mix(in oklab, var(--color-fd-primary) 10%, transparent)";
|
"color-mix(in oklab, var(--color-fd-primary) 10%, transparent)";
|
||||||
|
const { Card, Cards } = defaultMdxComponents;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
@ -27,9 +35,36 @@ export default function HomePage() {
|
||||||
"repeating-linear-gradient(to bottom, transparent, color-mix(in oklab, var(--color-fd-primary) 1%, transparent) 500px, transparent 1000px)",
|
"repeating-linear-gradient(to bottom, transparent, color-mix(in oklab, var(--color-fd-primary) 1%, transparent) 500px, transparent 1000px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative mb-4">
|
||||||
<Hero />
|
<Hero />
|
||||||
</div>
|
</div>
|
||||||
|
<Cards>
|
||||||
|
<Card
|
||||||
|
icon={<ZapIcon />}
|
||||||
|
href="/docs/quick-start"
|
||||||
|
title="Quick start"
|
||||||
|
>
|
||||||
|
Get started with ckan-devstaller and install CKAN within minutes
|
||||||
|
</Card>
|
||||||
|
<Card icon={<BlocksIcon />} href="/docs/builder" title="Builder">
|
||||||
|
Customize your installation with an interactive web GUI
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
icon={<HomeIcon />}
|
||||||
|
href="/docs/reference/installation-architecture"
|
||||||
|
title="Installation architecture"
|
||||||
|
>
|
||||||
|
Learn about where files are installed after running
|
||||||
|
ckan-devstaller
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
icon={<GitMergeIcon />}
|
||||||
|
href="https://github.com/dathere/ckan-devstaller"
|
||||||
|
title="Source code"
|
||||||
|
>
|
||||||
|
View the source code of ckan-devstaller on GitHub
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|
@ -59,9 +94,12 @@ function Hero() {
|
||||||
/>
|
/>
|
||||||
<h1 className="mb-8 text-4xl font-medium md:hidden">ckan-devstaller</h1>
|
<h1 className="mb-8 text-4xl font-medium md:hidden">ckan-devstaller</h1>
|
||||||
<h1 className="mb-8 max-w-[800px] text-4xl font-medium max-md:hidden">
|
<h1 className="mb-8 max-w-[800px] text-4xl font-medium max-md:hidden">
|
||||||
<span className="text-5xl">ckan-devstaller</span>
|
<span className="text-5xl">
|
||||||
|
ckan-devstaller{" "}
|
||||||
|
<SailboatIcon className="inline-block w-10 h-10 pb-1" />
|
||||||
|
</span>
|
||||||
<br />
|
<br />
|
||||||
Launch CKAN dev instances within minutes
|
Launch CKAN dev instances within minutes.
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mb-2 text-fd-muted-foreground md:max-w-[80%] md:text-xl">
|
<p className="mb-2 text-fd-muted-foreground md:max-w-[80%] md:text-xl">
|
||||||
ckan-devstaller is a command-line tool to automate installing CKAN for
|
ckan-devstaller is a command-line tool to automate installing CKAN for
|
||||||
|
|
@ -81,7 +119,7 @@ function Hero() {
|
||||||
buttonVariants({ size: "lg", className: "rounded-full" }),
|
buttonVariants({ size: "lg", className: "rounded-full" }),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Getting Started
|
Get Started
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/dathere/ckan-devstaller"
|
href="https://github.com/dathere/ckan-devstaller"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,14 @@
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": true,
|
"ignoreUnknown": true,
|
||||||
"includes": ["**", "!node_modules", "!.next", "!dist", "!build", "!.source"]
|
"includes": [
|
||||||
|
"**",
|
||||||
|
"!node_modules",
|
||||||
|
"!.next",
|
||||||
|
"!dist",
|
||||||
|
"!build",
|
||||||
|
"!.source"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
@ -31,4 +38,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-accordion": "^1.2.12",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-collapsible": "^1.1.12",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"fumadocs-core": "15.8.1",
|
"fumadocs-core": "15.8.1",
|
||||||
"fumadocs-mdx": "12.0.1",
|
"fumadocs-mdx": "12.0.1",
|
||||||
|
|
@ -17,7 +18,7 @@
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.2.4",
|
"@biomejs/biome": "2.2.5",
|
||||||
"@tailwindcss/postcss": "^4.1.13",
|
"@tailwindcss/postcss": "^4.1.13",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "24.5.2",
|
"@types/node": "24.5.2",
|
||||||
|
|
@ -32,23 +33,23 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||||
|
|
||||||
"@biomejs/biome": ["@biomejs/biome@2.2.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.4", "@biomejs/cli-darwin-x64": "2.2.4", "@biomejs/cli-linux-arm64": "2.2.4", "@biomejs/cli-linux-arm64-musl": "2.2.4", "@biomejs/cli-linux-x64": "2.2.4", "@biomejs/cli-linux-x64-musl": "2.2.4", "@biomejs/cli-win32-arm64": "2.2.4", "@biomejs/cli-win32-x64": "2.2.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg=="],
|
"@biomejs/biome": ["@biomejs/biome@2.2.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.5", "@biomejs/cli-darwin-x64": "2.2.5", "@biomejs/cli-linux-arm64": "2.2.5", "@biomejs/cli-linux-arm64-musl": "2.2.5", "@biomejs/cli-linux-x64": "2.2.5", "@biomejs/cli-linux-x64-musl": "2.2.5", "@biomejs/cli-win32-arm64": "2.2.5", "@biomejs/cli-win32-x64": "2.2.5" }, "bin": { "biome": "bin/biome" } }, "sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw=="],
|
||||||
|
|
||||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA=="],
|
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MYT+nZ38wEIWVcL5xLyOhYQQ7nlWD0b/4mgATW2c8dvq7R4OQjt/XGXFkXrmtWmQofaIM14L7V8qIz/M+bx5QQ=="],
|
||||||
|
|
||||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg=="],
|
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-FLIEl73fv0R7dI10EnEiZLw+IMz3mWLnF95ASDI0kbx6DDLJjWxE5JxxBfmG+udz1hIDd3fr5wsuP7nwuTRdAg=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw=="],
|
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5DjiiDfHqGgR2MS9D+AZ8kOfrzTGqLKywn8hoXpXXlJXIECGQ32t+gt/uiS2XyGBM2XQhR6ztUvbjZWeccFMoQ=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ=="],
|
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Ov2wgAFwqDvQiESnu7b9ufD1faRa+40uwrohgBopeY84El2TnBDoMNXx6iuQdreoFGjwW8vH6k68G21EpNERw=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ=="],
|
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-fq9meKm1AEXeAWan3uCg6XSP5ObA6F/Ovm89TwaMiy1DNIwdgxPkNwxlXJX8iM6oRbFysYeGnT0OG8diCWb9ew=="],
|
||||||
|
|
||||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg=="],
|
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-AVqLCDb/6K7aPNIcxHaTQj01sl1m989CJIQFQEaiQkGr2EQwyOpaATJ473h+nXDUuAcREhccfRpe/tu+0wu0eQ=="],
|
||||||
|
|
||||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ=="],
|
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-xaOIad4wBambwJa6mdp1FigYSIF9i7PCqRbvBqtIi9y29QtPVQ13sDGtUnsRoe6SjL10auMzQ6YAe+B3RpZXVg=="],
|
||||||
|
|
||||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg=="],
|
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.5", "", { "os": "win32", "cpu": "x64" }, "sha512-F/jhuXCssPFAuciMhHKk00xnCAxJRS/pUzVfXYmOMUp//XW7mO6QeCjsjvnm8L4AO/dG2VOB0O+fJPiJ2uXtIw=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||||
|
|
||||||
|
|
|
||||||
76
docs/components/builder.tsx
Normal file
76
docs/components/builder.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { CodeBlock, Pre } from "fumadocs-ui/components/codeblock";
|
||||||
|
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||||
|
import {
|
||||||
|
DocsBody,
|
||||||
|
DocsDescription,
|
||||||
|
DocsPage,
|
||||||
|
DocsTitle,
|
||||||
|
} from "fumadocs-ui/page";
|
||||||
|
import {
|
||||||
|
BarChartBigIcon,
|
||||||
|
BlocksIcon,
|
||||||
|
HomeIcon,
|
||||||
|
SailboatIcon,
|
||||||
|
TerminalSquareIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
export default function Builder() {
|
||||||
|
const { Card, Cards } = defaultMdxComponents;
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div className="col-span-1 border-r-2 pr-4">
|
||||||
|
<div className="fixed">
|
||||||
|
<h2>ckan-devstaller command</h2>
|
||||||
|
<CodeBlock title="Installation command">
|
||||||
|
<Pre className="text-wrap pl-4 max-w-[21rem]">
|
||||||
|
./ckan-devstaller
|
||||||
|
</Pre>
|
||||||
|
</CodeBlock>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<h2>Configuration options</h2>
|
||||||
|
<h3>Presets</h3>
|
||||||
|
<Cards className="grid-cols-2">
|
||||||
|
<Card
|
||||||
|
className="bg-blue-100 dark:bg-blue-950 border-blue-300 dark:border-blue-900 border-2"
|
||||||
|
icon={<SailboatIcon />}
|
||||||
|
title="CKAN-only"
|
||||||
|
>
|
||||||
|
Installs CKAN with ckan-compose.
|
||||||
|
</Card>
|
||||||
|
<Card icon={<BlocksIcon />} title="CKAN and the DataStore extension">
|
||||||
|
Installs CKAN and the DataStore extension.
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
icon={<BarChartBigIcon />}
|
||||||
|
title="CKAN, DataStore, ckanext-scheming, and DataPusher+ extensions"
|
||||||
|
>
|
||||||
|
Installs CKAN, the DataStore extension, the ckanext-scheming
|
||||||
|
extension, and the DataPusher+ extension.
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
<h3>CKAN version</h3>
|
||||||
|
<Cards>
|
||||||
|
<Card icon={<SailboatIcon />} title="2.11.3"></Card>
|
||||||
|
<Card icon={<SailboatIcon />} title="2.10.8"></Card>
|
||||||
|
<Card icon={<SailboatIcon />} title="Other"></Card>
|
||||||
|
</Cards>
|
||||||
|
<h3>SSH capability</h3>
|
||||||
|
<Cards>
|
||||||
|
<Card icon={<TerminalSquareIcon />} title="Enable SSH">
|
||||||
|
Installs openssh-server and net-tools.
|
||||||
|
</Card>
|
||||||
|
</Cards>
|
||||||
|
<h3>CKAN extensions</h3>
|
||||||
|
<Cards>
|
||||||
|
<Card icon={<TerminalSquareIcon />} title="ckanext-scheming"></Card>
|
||||||
|
<Card icon={<TerminalSquareIcon />} title="ckanext-gztr"></Card>
|
||||||
|
<Card icon={<TerminalSquareIcon />} title="DataStore"></Card>
|
||||||
|
<Card icon={<TerminalSquareIcon />} title="DataPusher+"></Card>
|
||||||
|
<Card icon={<TerminalSquareIcon />} title="ckanext-spatial"></Card>
|
||||||
|
</Cards>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
261
docs/components/codeblock.tsx
Normal file
261
docs/components/codeblock.tsx
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
'use client';
|
||||||
|
import { Check, Clipboard } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
type ComponentProps,
|
||||||
|
createContext,
|
||||||
|
type HTMLAttributes,
|
||||||
|
type ReactNode,
|
||||||
|
type RefObject,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
import { cn } from '../lib/cn';
|
||||||
|
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button';
|
||||||
|
import { buttonVariants } from './ui/button';
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from './tabs.unstyled';
|
||||||
|
import { mergeRefs } from '../lib/merge-refs';
|
||||||
|
|
||||||
|
export interface CodeBlockProps extends ComponentProps<'figure'> {
|
||||||
|
/**
|
||||||
|
* Icon of code block
|
||||||
|
*
|
||||||
|
* When passed as a string, it assumes the value is the HTML of icon
|
||||||
|
*/
|
||||||
|
icon?: ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to copy code with copy button
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
allowCopy?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep original background color generated by Shiki or Rehype Code
|
||||||
|
*
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
keepBackground?: boolean;
|
||||||
|
|
||||||
|
viewportProps?: HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show line numbers
|
||||||
|
*/
|
||||||
|
'data-line-numbers'?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defaultValue 1
|
||||||
|
*/
|
||||||
|
'data-line-numbers-start'?: number;
|
||||||
|
|
||||||
|
Actions?: (props: { className?: string; children?: ReactNode }) => ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabsContext = createContext<{
|
||||||
|
containerRef: RefObject<HTMLDivElement | null>;
|
||||||
|
nested: boolean;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
export function Pre(props: ComponentProps<'pre'>) {
|
||||||
|
return (
|
||||||
|
<pre
|
||||||
|
{...props}
|
||||||
|
className={cn('min-w-full w-max *:flex *:flex-col', props.className)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CodeBlock({
|
||||||
|
ref,
|
||||||
|
title,
|
||||||
|
allowCopy = true,
|
||||||
|
keepBackground = false,
|
||||||
|
icon,
|
||||||
|
viewportProps = {},
|
||||||
|
children,
|
||||||
|
Actions = (props) => (
|
||||||
|
<div {...props} className={cn('empty:hidden', props.className)} />
|
||||||
|
),
|
||||||
|
...props
|
||||||
|
}: CodeBlockProps) {
|
||||||
|
const inTab = useContext(TabsContext) !== null;
|
||||||
|
const areaRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<figure
|
||||||
|
ref={ref}
|
||||||
|
dir="ltr"
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
inTab
|
||||||
|
? 'bg-fd-secondary -mx-px -mb-px last:rounded-b-xl'
|
||||||
|
: 'my-4 bg-fd-card rounded-xl',
|
||||||
|
keepBackground && 'bg-(--shiki-light-bg) dark:bg-(--shiki-dark-bg)',
|
||||||
|
|
||||||
|
'shiki relative border shadow-sm outline-none not-prose overflow-hidden text-sm',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{title ? (
|
||||||
|
<div className="flex text-fd-muted-foreground items-center gap-2 h-9.5 border-b px-4">
|
||||||
|
{typeof icon === 'string' ? (
|
||||||
|
<div
|
||||||
|
className="[&_svg]:size-3.5"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: icon,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
icon
|
||||||
|
)}
|
||||||
|
<figcaption className="flex-1 truncate">{title}</figcaption>
|
||||||
|
{Actions({
|
||||||
|
className: '-me-2',
|
||||||
|
children: allowCopy && <CopyButton containerRef={areaRef} />,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
Actions({
|
||||||
|
className:
|
||||||
|
'absolute top-2 right-2 z-2 backdrop-blur-lg rounded-lg text-fd-muted-foreground',
|
||||||
|
children: allowCopy && <CopyButton containerRef={areaRef} />,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
ref={areaRef}
|
||||||
|
{...viewportProps}
|
||||||
|
className={cn(
|
||||||
|
'text-[13px] py-3.5 overflow-auto max-h-[600px] fd-scroll-container',
|
||||||
|
viewportProps.className,
|
||||||
|
)}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
// space for toolbar
|
||||||
|
'--padding-right': !title ? 'calc(var(--spacing) * 8)' : undefined,
|
||||||
|
counterSet: props['data-line-numbers']
|
||||||
|
? `line ${Number(props['data-line-numbers-start'] ?? 1) - 1}`
|
||||||
|
: undefined,
|
||||||
|
...viewportProps.style,
|
||||||
|
} as object
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</figure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyButton({
|
||||||
|
className,
|
||||||
|
containerRef,
|
||||||
|
...props
|
||||||
|
}: ComponentProps<'button'> & {
|
||||||
|
containerRef: RefObject<HTMLElement | null>;
|
||||||
|
}) {
|
||||||
|
const [checked, onClick] = useCopyButton(() => {
|
||||||
|
const pre = containerRef.current?.getElementsByTagName('pre').item(0);
|
||||||
|
if (!pre) return;
|
||||||
|
|
||||||
|
const clone = pre.cloneNode(true) as HTMLElement;
|
||||||
|
clone.querySelectorAll('.nd-copy-ignore').forEach((node) => {
|
||||||
|
node.replaceWith('\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
void navigator.clipboard.writeText(clone.textContent ?? '');
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-checked={checked || undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
className:
|
||||||
|
'hover:text-fd-accent-foreground data-[checked]:text-fd-accent-foreground',
|
||||||
|
size: 'icon-xs',
|
||||||
|
}),
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
aria-label={checked ? 'Copied Text' : 'Copy Text'}
|
||||||
|
onClick={onClick}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{checked ? <Check /> : <Clipboard />}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CodeBlockTabs({ ref, ...props }: ComponentProps<typeof Tabs>) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const nested = useContext(TabsContext) !== null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
ref={mergeRefs(containerRef, ref)}
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
'bg-fd-card rounded-xl border',
|
||||||
|
!nested && 'my-4',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<TabsContext.Provider
|
||||||
|
value={useMemo(
|
||||||
|
() => ({
|
||||||
|
containerRef,
|
||||||
|
nested,
|
||||||
|
}),
|
||||||
|
[nested],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</TabsContext.Provider>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CodeBlockTabsList(props: ComponentProps<typeof TabsList>) {
|
||||||
|
return (
|
||||||
|
<TabsList
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-row px-2 overflow-x-auto text-fd-muted-foreground',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</TabsList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CodeBlockTabsTrigger({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: ComponentProps<typeof TabsTrigger>) {
|
||||||
|
return (
|
||||||
|
<TabsTrigger
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
'relative group inline-flex text-sm font-medium text-nowrap items-center transition-colors gap-2 px-2 py-1.5 hover:text-fd-accent-foreground data-[state=active]:text-fd-primary [&_svg]:size-3.5',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-x-2 bottom-0 h-px group-data-[state=active]:bg-fd-primary" />
|
||||||
|
{children}
|
||||||
|
</TabsTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: currently Vite RSC plugin has problem with `asChild` due to children is automatically wrapped in <Fragment />, maybe revisit this in future
|
||||||
|
export function CodeBlockTab(props: ComponentProps<typeof TabsContent>) {
|
||||||
|
return <TabsContent {...props} />;
|
||||||
|
}
|
||||||
202
docs/components/tabs.tsx
Normal file
202
docs/components/tabs.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
type ComponentProps,
|
||||||
|
createContext,
|
||||||
|
type ReactNode,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useId,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { cn } from '../lib/cn';
|
||||||
|
import * as Unstyled from './tabs.unstyled';
|
||||||
|
|
||||||
|
type CollectionKey = string | symbol;
|
||||||
|
|
||||||
|
export interface TabsProps
|
||||||
|
extends Omit<
|
||||||
|
ComponentProps<typeof Unstyled.Tabs>,
|
||||||
|
'value' | 'onValueChange'
|
||||||
|
> {
|
||||||
|
/**
|
||||||
|
* Use simple mode instead of advanced usage as documented in https://radix-ui.com/primitives/docs/components/tabs.
|
||||||
|
*/
|
||||||
|
items?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for `defaultValue` when `items` is provided.
|
||||||
|
*
|
||||||
|
* @defaultValue 0
|
||||||
|
*/
|
||||||
|
defaultIndex?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional label in tabs list when `items` is provided.
|
||||||
|
*/
|
||||||
|
label?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabsContext = createContext<{
|
||||||
|
items?: string[];
|
||||||
|
collection: CollectionKey[];
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
function useTabContext() {
|
||||||
|
const ctx = useContext(TabsContext);
|
||||||
|
if (!ctx) throw new Error('You must wrap your component in <Tabs>');
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TabsList = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof Unstyled.TabsList>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof Unstyled.TabsList>
|
||||||
|
>((props, ref) => (
|
||||||
|
<Unstyled.TabsList
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
'flex gap-3.5 text-fd-secondary-foreground overflow-x-auto px-4 not-prose',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsList.displayName = 'TabsList';
|
||||||
|
|
||||||
|
export const TabsTrigger = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof Unstyled.TabsTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof Unstyled.TabsTrigger>
|
||||||
|
>((props, ref) => (
|
||||||
|
<Unstyled.TabsTrigger
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center gap-2 whitespace-nowrap text-fd-muted-foreground border-b border-transparent py-2 text-sm font-medium transition-colors [&_svg]:size-4 hover:text-fd-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=active]:border-fd-primary data-[state=active]:text-fd-primary',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsTrigger.displayName = 'TabsTrigger';
|
||||||
|
|
||||||
|
export function Tabs({
|
||||||
|
ref,
|
||||||
|
className,
|
||||||
|
items,
|
||||||
|
label,
|
||||||
|
defaultIndex = 0,
|
||||||
|
defaultValue = items ? escapeValue(items[defaultIndex]) : undefined,
|
||||||
|
...props
|
||||||
|
}: TabsProps) {
|
||||||
|
const [value, setValue] = useState(defaultValue);
|
||||||
|
const collection = useMemo<CollectionKey[]>(() => [], []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Unstyled.Tabs
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col overflow-hidden rounded-xl border bg-fd-secondary my-4',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
value={value}
|
||||||
|
onValueChange={(v: string) => {
|
||||||
|
if (items && !items.some((item) => escapeValue(item) === v)) return;
|
||||||
|
setValue(v);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{items && (
|
||||||
|
<TabsList>
|
||||||
|
{label && (
|
||||||
|
<span className="text-sm font-medium my-auto me-auto">{label}</span>
|
||||||
|
)}
|
||||||
|
{items.map((item) => (
|
||||||
|
<TabsTrigger key={item} value={escapeValue(item)}>
|
||||||
|
{item}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
)}
|
||||||
|
<TabsContext.Provider
|
||||||
|
value={useMemo(() => ({ items, collection }), [collection, items])}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</TabsContext.Provider>
|
||||||
|
</Unstyled.Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabProps
|
||||||
|
extends Omit<ComponentProps<typeof Unstyled.TabsContent>, 'value'> {
|
||||||
|
/**
|
||||||
|
* Value of tab, detect from index if unspecified.
|
||||||
|
*/
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Tab({ value, ...props }: TabProps) {
|
||||||
|
const { items } = useTabContext();
|
||||||
|
const resolved =
|
||||||
|
value ??
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks -- `value` is not supposed to change
|
||||||
|
items?.at(useCollectionIndex());
|
||||||
|
if (!resolved)
|
||||||
|
throw new Error(
|
||||||
|
'Failed to resolve tab `value`, please pass a `value` prop to the Tab component.',
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabsContent value={escapeValue(resolved)} {...props}>
|
||||||
|
{props.children}
|
||||||
|
</TabsContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TabsContent({
|
||||||
|
value,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: ComponentProps<typeof Unstyled.TabsContent>) {
|
||||||
|
return (
|
||||||
|
<Unstyled.TabsContent
|
||||||
|
value={value}
|
||||||
|
forceMount
|
||||||
|
className={cn(
|
||||||
|
'p-4 text-[15px] bg-fd-background rounded-xl outline-none prose-no-margin data-[state=inactive]:hidden [&>figure:only-child]:-m-4 [&>figure:only-child]:border-none',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Unstyled.TabsContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired by Headless UI.
|
||||||
|
*
|
||||||
|
* Return the index of children, this is made possible by registering the order of render from children using React context.
|
||||||
|
* This is supposed by work with pre-rendering & pure client-side rendering.
|
||||||
|
*/
|
||||||
|
function useCollectionIndex() {
|
||||||
|
const key = useId();
|
||||||
|
const { collection } = useTabContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
const idx = collection.indexOf(key);
|
||||||
|
if (idx !== -1) collection.splice(idx, 1);
|
||||||
|
};
|
||||||
|
}, [key, collection]);
|
||||||
|
|
||||||
|
if (!collection.includes(key)) collection.push(key);
|
||||||
|
return collection.indexOf(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* only escape whitespaces in values in simple mode
|
||||||
|
*/
|
||||||
|
function escapeValue(v: string): string {
|
||||||
|
return v.toLowerCase().replace(/\s/, '-');
|
||||||
|
}
|
||||||
163
docs/components/tabs.unstyled.tsx
Normal file
163
docs/components/tabs.unstyled.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type ComponentProps,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import * as Primitive from '@radix-ui/react-tabs';
|
||||||
|
import { mergeRefs } from '../lib/merge-refs';
|
||||||
|
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
|
||||||
|
|
||||||
|
type ChangeListener = (v: string) => void;
|
||||||
|
const listeners = new Map<string, ChangeListener[]>();
|
||||||
|
|
||||||
|
function addChangeListener(id: string, listener: ChangeListener): void {
|
||||||
|
const list = listeners.get(id) ?? [];
|
||||||
|
list.push(listener);
|
||||||
|
listeners.set(id, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeChangeListener(id: string, listener: ChangeListener): void {
|
||||||
|
const list = listeners.get(id) ?? [];
|
||||||
|
listeners.set(
|
||||||
|
id,
|
||||||
|
list.filter((item) => item !== listener),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabsProps extends ComponentProps<typeof Primitive.Tabs> {
|
||||||
|
/**
|
||||||
|
* Identifier for Sharing value of tabs
|
||||||
|
*/
|
||||||
|
groupId?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable persistent
|
||||||
|
*/
|
||||||
|
persist?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, updates the URL hash based on the tab's id
|
||||||
|
*/
|
||||||
|
updateAnchor?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabsContext = createContext<{
|
||||||
|
valueToIdMap: Map<string, string>;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
function useTabContext() {
|
||||||
|
const ctx = useContext(TabsContext);
|
||||||
|
if (!ctx) throw new Error('You must wrap your component in <Tabs>');
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TabsList = Primitive.TabsList;
|
||||||
|
|
||||||
|
export const TabsTrigger = Primitive.TabsTrigger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal You better not use it
|
||||||
|
*/
|
||||||
|
export function Tabs({
|
||||||
|
ref,
|
||||||
|
groupId,
|
||||||
|
persist = false,
|
||||||
|
updateAnchor = false,
|
||||||
|
defaultValue,
|
||||||
|
value: _value,
|
||||||
|
onValueChange: _onValueChange,
|
||||||
|
...props
|
||||||
|
}: TabsProps) {
|
||||||
|
const tabsRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [value, setValue] =
|
||||||
|
_value === undefined
|
||||||
|
? // eslint-disable-next-line react-hooks/rules-of-hooks -- not supposed to change controlled/uncontrolled
|
||||||
|
useState(defaultValue)
|
||||||
|
: [_value, _onValueChange ?? (() => undefined)];
|
||||||
|
|
||||||
|
const onChange = useEffectEvent((v: string) => setValue(v));
|
||||||
|
const valueToIdMap = useMemo(() => new Map<string, string>(), []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!groupId) return;
|
||||||
|
const previous = persist
|
||||||
|
? localStorage.getItem(groupId)
|
||||||
|
: sessionStorage.getItem(groupId);
|
||||||
|
|
||||||
|
if (previous) onChange(previous);
|
||||||
|
addChangeListener(groupId, onChange);
|
||||||
|
return () => {
|
||||||
|
removeChangeListener(groupId, onChange);
|
||||||
|
};
|
||||||
|
}, [groupId, persist]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const hash = window.location.hash.slice(1);
|
||||||
|
if (!hash) return;
|
||||||
|
|
||||||
|
for (const [value, id] of valueToIdMap.entries()) {
|
||||||
|
if (id === hash) {
|
||||||
|
onChange(value);
|
||||||
|
tabsRef.current?.scrollIntoView();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [valueToIdMap]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Primitive.Tabs
|
||||||
|
ref={mergeRefs(ref, tabsRef)}
|
||||||
|
value={value}
|
||||||
|
onValueChange={(v: string) => {
|
||||||
|
if (updateAnchor) {
|
||||||
|
const id = valueToIdMap.get(v);
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
window.history.replaceState(null, '', `#${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupId) {
|
||||||
|
listeners.get(groupId)?.forEach((item) => {
|
||||||
|
item(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (persist) localStorage.setItem(groupId, v);
|
||||||
|
else sessionStorage.setItem(groupId, v);
|
||||||
|
} else {
|
||||||
|
setValue(v);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<TabsContext.Provider
|
||||||
|
value={useMemo(() => ({ valueToIdMap }), [valueToIdMap])}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</TabsContext.Provider>
|
||||||
|
</Primitive.Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TabsContent({
|
||||||
|
value,
|
||||||
|
...props
|
||||||
|
}: ComponentProps<typeof Primitive.TabsContent>) {
|
||||||
|
const { valueToIdMap } = useTabContext();
|
||||||
|
|
||||||
|
if (props.id) {
|
||||||
|
valueToIdMap.set(value, props.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Primitive.TabsContent value={value} {...props}>
|
||||||
|
{props.children}
|
||||||
|
</Primitive.TabsContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
8
docs/content/docs/builder.mdx
Normal file
8
docs/content/docs/builder.mdx
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
title: Builder
|
||||||
|
description: Customize your CKAN installation before running ckan-devstaller.
|
||||||
|
---
|
||||||
|
|
||||||
|
import Builder from "@/components/builder";
|
||||||
|
|
||||||
|
<Builder />
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
title: Quick Start
|
title: Quick Start
|
||||||
description: Getting Started with ckan-devstaller
|
description: Get started with ckan-devstaller and install CKAN within minutes.
|
||||||
---
|
---
|
||||||
|
|
||||||
ckan-devstaller attempts to install a CKAN instance from source along with [ckan-compose](https://github.com/tino097/ckan-compose) and other optional features, intended for development use in a new Ubuntu 22.04 instance.
|
ckan-devstaller attempts to install a CKAN instance from source along with [ckan-compose](https://github.com/tino097/ckan-compose) and other optional features, intended for development use in a new Ubuntu 22.04 instance.
|
||||||
|
|
||||||
<Callout title="Please run ckan-devstaller in a new Ubuntu 22.04 instance only" type="error">Make sure `ckan-devstaller` is run in a **new** Ubuntu 22.04 instanceof. Do NOT run `ckan-devstaller` in an existing instance that is important for your usage.</Callout>
|
<Callout title="Please run ckan-devstaller in a new Ubuntu 22.04 instance only" type="error">Make sure `ckan-devstaller` is run in a **new** Ubuntu 22.04 instance. Do NOT run `ckan-devstaller` in an existing instance that is important for your usage.</Callout>
|
||||||
|
|
||||||
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
||||||
|
|
||||||
|
|
@ -40,11 +40,18 @@ wget -O - https://github.com/dathere/ckan-devstaller/releases/download/0.2.1/ins
|
||||||
|
|
||||||
## Learn more
|
## Learn more
|
||||||
|
|
||||||
import { BlocksIcon, GitMergeIcon, Trash2Icon } from 'lucide-react';
|
import { BlocksIcon, HomeIcon, GitMergeIcon, Trash2Icon } from 'lucide-react';
|
||||||
|
|
||||||
<Cards>
|
<Cards>
|
||||||
<Card
|
<Card
|
||||||
icon={<BlocksIcon />}
|
icon={<BlocksIcon />}
|
||||||
|
href="/docs/builder"
|
||||||
|
title="Builder"
|
||||||
|
>
|
||||||
|
Customize your installation with an interactive web GUI
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
icon={<HomeIcon />}
|
||||||
href="/docs/reference/installation-architecture"
|
href="/docs/reference/installation-architecture"
|
||||||
title="Installation architecture"
|
title="Installation architecture"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-accordion": "^1.2.12",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-collapsible": "^1.1.12",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"fumadocs-core": "15.8.1",
|
"fumadocs-core": "15.8.1",
|
||||||
"fumadocs-mdx": "12.0.1",
|
"fumadocs-mdx": "12.0.1",
|
||||||
|
|
@ -24,14 +25,14 @@
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.2.5",
|
||||||
|
"@tailwindcss/postcss": "^4.1.13",
|
||||||
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "24.5.2",
|
"@types/node": "24.5.2",
|
||||||
"@types/react": "^19.1.14",
|
"@types/react": "^19.1.14",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.9",
|
||||||
"typescript": "^5.9.2",
|
|
||||||
"@types/mdx": "^2.0.13",
|
|
||||||
"@tailwindcss/postcss": "^4.1.13",
|
|
||||||
"tailwindcss": "^4.1.13",
|
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"@biomejs/biome": "^2.2.4"
|
"tailwindcss": "^4.1.13",
|
||||||
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue