├── .changeset
└── config.json
├── .github
├── CONTRIBUTING.md
├── actions
│ └── install-dependencies
│ │ └── action.yml
├── dependabot.yml
├── vocs-logo-dark.svg
├── vocs-logo-light.svg
└── workflows
│ ├── canary.yml
│ ├── changesets.yml
│ ├── on-pull-request.yml
│ ├── on-push-to-main.yml
│ ├── prune-tags.yml
│ └── verify.yml
├── .gitignore
├── .npmrc
├── .vscode
├── extensions.json
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── api
└── og.tsx
├── biome.json
├── create-vocs
├── CHANGELOG.md
├── bin.ts
├── index.ts
├── init.ts
├── package.json
├── templates
│ └── default
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── docs
│ │ └── pages
│ │ │ ├── example.mdx
│ │ │ ├── getting-started.mdx
│ │ │ └── index.mdx
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── vocs.config.ts
└── tsconfig.build.json
├── package.json
├── playgrounds
├── config-json
│ ├── docs
│ │ └── pages
│ │ │ └── index.mdx
│ ├── package.json
│ └── vocs.config.json
├── config-toml
│ ├── Vocs.toml
│ ├── docs
│ │ └── pages
│ │ │ └── index.mdx
│ └── package.json
├── custom-layout
│ ├── docs
│ │ ├── layout.tsx
│ │ ├── pages
│ │ │ ├── index.mdx
│ │ │ └── subpage.mdx
│ │ └── styles.css
│ ├── package.json
│ └── vocs.config.ts
├── default
│ ├── docs
│ │ └── pages
│ │ │ └── index.mdx
│ ├── package.json
│ └── vocs.config.ts
└── tailwind
│ ├── docs
│ ├── pages
│ │ └── index.mdx
│ └── styles.css
│ └── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
├── postbuild.ts
├── preconstruct.ts
└── prepublish.ts
├── site
├── components
│ ├── Example.tsx
│ └── MyButton.tsx
├── footer.tsx
├── pages
│ ├── blank.mdx
│ ├── blog
│ │ ├── gm.mdx
│ │ └── index.mdx
│ ├── docs
│ │ ├── api
│ │ │ ├── config.mdx
│ │ │ └── frontmatter.mdx
│ │ ├── guides
│ │ │ ├── blog.mdx
│ │ │ ├── code-snippets.mdx
│ │ │ ├── components.mdx
│ │ │ ├── layouts.mdx
│ │ │ ├── markdown-snippets.mdx
│ │ │ ├── navigation.mdx
│ │ │ ├── og-images.mdx
│ │ │ ├── styling.mdx
│ │ │ ├── theming.mdx
│ │ │ └── twoslash.mdx
│ │ ├── index.mdx
│ │ ├── kitchen-sink.mdx
│ │ ├── markdown.mdx
│ │ └── structure.mdx
│ └── index.mdx
├── public
│ ├── og.png
│ ├── vocs-icon-dark.svg
│ ├── vocs-icon-light.svg
│ ├── vocs-logo-dark.svg
│ └── vocs-logo-light.svg
├── snippets
│ └── example.ts
└── styles.css
├── src
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
│ ├── components
│ │ ├── AiCtaDropdown.css.ts
│ │ ├── AiCtaDropdown.tsx
│ │ ├── Authors.css.ts
│ │ ├── Authors.tsx
│ │ ├── Banner.css.ts
│ │ ├── Banner.tsx
│ │ ├── BlogPosts.css.ts
│ │ ├── BlogPosts.tsx
│ │ ├── Button.css.ts
│ │ ├── Button.tsx
│ │ ├── Callout.css.ts
│ │ ├── Callout.tsx
│ │ ├── Content.css.ts
│ │ ├── Content.tsx
│ │ ├── CopyButton.css.ts
│ │ ├── CopyButton.tsx
│ │ ├── DesktopSearch.css.ts
│ │ ├── DesktopSearch.tsx
│ │ ├── DesktopTopNav.css.ts
│ │ ├── DesktopTopNav.tsx
│ │ ├── ExternalLink.css.ts
│ │ ├── ExternalLink.tsx
│ │ ├── Footer.css.ts
│ │ ├── Footer.tsx
│ │ ├── HomePage.css.ts
│ │ ├── HomePage.tsx
│ │ ├── Icon.css.ts
│ │ ├── Icon.tsx
│ │ ├── KeyboardShortcut.css.ts
│ │ ├── KeyboardShortcut.tsx
│ │ ├── Link.css.ts
│ │ ├── Link.tsx
│ │ ├── Logo.css.ts
│ │ ├── Logo.tsx
│ │ ├── MobileSearch.css.ts
│ │ ├── MobileSearch.tsx
│ │ ├── MobileTopNav.css.ts
│ │ ├── MobileTopNav.tsx
│ │ ├── NavLogo.css.ts
│ │ ├── NavLogo.tsx
│ │ ├── NavigationMenu.css.ts
│ │ ├── NavigationMenu.tsx
│ │ ├── NotFound.css.ts
│ │ ├── NotFound.tsx
│ │ ├── Outline.css.ts
│ │ ├── Outline.tsx
│ │ ├── Popover.css.ts
│ │ ├── Popover.tsx
│ │ ├── Raw.tsx
│ │ ├── RouterLink.tsx
│ │ ├── SearchDialog.css.ts
│ │ ├── SearchDialog.tsx
│ │ ├── Sidebar.css.ts
│ │ ├── Sidebar.tsx
│ │ ├── SkipLink.css.ts
│ │ ├── SkipLink.tsx
│ │ ├── Socials.css.ts
│ │ ├── Socials.tsx
│ │ ├── Sponsors.css.ts
│ │ ├── Sponsors.tsx
│ │ ├── Step.css.ts
│ │ ├── Step.tsx
│ │ ├── Steps.css.ts
│ │ ├── Steps.tsx
│ │ ├── Tabs.css.ts
│ │ ├── Tabs.tsx
│ │ ├── ThemeToggle.css.ts
│ │ ├── ThemeToggle.tsx
│ │ ├── icons
│ │ │ ├── ArrowDiagonal.tsx
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── CheckCircle.tsx
│ │ │ ├── Checkmark.tsx
│ │ │ ├── ChevronDown.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUp.tsx
│ │ │ ├── Copy.tsx
│ │ │ ├── Discord.tsx
│ │ │ ├── ExclamationTriangle.tsx
│ │ │ ├── File.tsx
│ │ │ ├── GitHub.tsx
│ │ │ ├── InfoCircled.tsx
│ │ │ ├── LightningBolt.tsx
│ │ │ ├── Link.tsx
│ │ │ ├── Menu.tsx
│ │ │ ├── Moon.tsx
│ │ │ ├── OpenAi.tsx
│ │ │ ├── Sun.tsx
│ │ │ ├── Telegram.tsx
│ │ │ ├── Terminal.tsx
│ │ │ ├── Warpcast.tsx
│ │ │ └── X.tsx
│ │ └── mdx
│ │ │ ├── Anchor.css.ts
│ │ │ ├── Anchor.tsx
│ │ │ ├── Aside.css.ts
│ │ │ ├── Aside.tsx
│ │ │ ├── Autolink.css.ts
│ │ │ ├── Autolink.tsx
│ │ │ ├── AutolinkIcon.css.ts
│ │ │ ├── AutolinkIcon.tsx
│ │ │ ├── Blockquote.css.ts
│ │ │ ├── Blockquote.tsx
│ │ │ ├── Code.css.ts
│ │ │ ├── Code.tsx
│ │ │ ├── CodeBlock.css.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeGroup.css.ts
│ │ │ ├── CodeGroup.tsx
│ │ │ ├── CodeTitle.css.ts
│ │ │ ├── CodeTitle.tsx
│ │ │ ├── Details.css.ts
│ │ │ ├── Details.tsx
│ │ │ ├── Div.css.ts
│ │ │ ├── Div.tsx
│ │ │ ├── Figcaption.css.ts
│ │ │ ├── Figcaption.tsx
│ │ │ ├── Figure.css.ts
│ │ │ ├── Figure.tsx
│ │ │ ├── Footnotes.css.ts
│ │ │ ├── Footnotes.tsx
│ │ │ ├── H1.css.ts
│ │ │ ├── H1.tsx
│ │ │ ├── H2.css.ts
│ │ │ ├── H2.tsx
│ │ │ ├── H3.css.ts
│ │ │ ├── H3.tsx
│ │ │ ├── H4.css.ts
│ │ │ ├── H4.tsx
│ │ │ ├── H5.css.ts
│ │ │ ├── H5.tsx
│ │ │ ├── H6.css.ts
│ │ │ ├── H6.tsx
│ │ │ ├── Header.css.ts
│ │ │ ├── Header.tsx
│ │ │ ├── Heading.css.ts
│ │ │ ├── Heading.tsx
│ │ │ ├── HorizontalRule.css.ts
│ │ │ ├── HorizontalRule.tsx
│ │ │ ├── Kbd.css.ts
│ │ │ ├── Kbd.tsx
│ │ │ ├── List.css.ts
│ │ │ ├── List.tsx
│ │ │ ├── ListItem.css.ts
│ │ │ ├── ListItem.tsx
│ │ │ ├── Paragraph.css.ts
│ │ │ ├── Paragraph.tsx
│ │ │ ├── Pre.css.ts
│ │ │ ├── Pre.tsx
│ │ │ ├── Section.css.ts
│ │ │ ├── Section.tsx
│ │ │ ├── Span.css.ts
│ │ │ ├── Span.tsx
│ │ │ ├── Steps.tsx
│ │ │ ├── Strong.css.ts
│ │ │ ├── Strong.tsx
│ │ │ ├── Subtitle.css.ts
│ │ │ ├── Subtitle.tsx
│ │ │ ├── Summary.css.ts
│ │ │ ├── Summary.tsx
│ │ │ ├── Table.css.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableCell.css.ts
│ │ │ ├── TableCell.tsx
│ │ │ ├── TableHeader.css.ts
│ │ │ ├── TableHeader.tsx
│ │ │ ├── TableRow.css.ts
│ │ │ ├── TableRow.tsx
│ │ │ ├── TwoslashPopover.tsx
│ │ │ └── index.tsx
│ ├── dom.d.ts
│ ├── hooks
│ │ ├── useActiveNavIds.ts
│ │ ├── useConfig.tsx
│ │ ├── useCopyCode.ts
│ │ ├── useDebounce.ts
│ │ ├── useEditLink.ts
│ │ ├── useLayout.tsx
│ │ ├── useLocalStorage.ts
│ │ ├── useMounted.ts
│ │ ├── useOgImageUrl.ts
│ │ ├── usePageData.ts
│ │ ├── useSearchIndex.ts
│ │ ├── useSidebar.ts
│ │ └── useTheme.ts
│ ├── index.client.tsx
│ ├── index.server.tsx
│ ├── layouts
│ │ ├── DocsLayout.css.ts
│ │ └── DocsLayout.tsx
│ ├── public
│ │ └── .vocs
│ │ │ └── icons
│ │ │ ├── arrow-diagonal.svg
│ │ │ ├── chevron-down.svg
│ │ │ ├── chevron-up.svg
│ │ │ └── link.svg
│ ├── root.tsx
│ ├── routes.tsx
│ ├── styles
│ │ ├── base.css.ts
│ │ ├── global.css.ts
│ │ ├── index.css.ts
│ │ ├── reset.css.ts
│ │ ├── twoslash.css.ts
│ │ ├── utils.css.ts
│ │ └── vars.css.ts
│ ├── types.ts
│ ├── utils
│ │ ├── createFetchRequest.ts
│ │ ├── debounce.ts
│ │ ├── deserializeElement.ts
│ │ ├── hydrateLazyRoutes.ts
│ │ ├── initializeTheme.ts
│ │ ├── mergeRefs.ts
│ │ └── removeTempStyles.ts
│ └── vite-env.d.ts
├── cli
│ ├── commands
│ │ ├── build.ts
│ │ ├── dev.ts
│ │ ├── preview.ts
│ │ ├── search-index.ts
│ │ └── twoslash.ts
│ ├── index.ts
│ └── version.ts
├── components.ts
├── config.ts
├── index.ts
├── mdx-react.ts
├── package.json
└── vite
│ ├── build.ts
│ ├── buildSearchIndex.ts
│ ├── buildTwoslash.ts
│ ├── devServer.ts
│ ├── index.html
│ ├── plugins
│ ├── css.ts
│ ├── dev.tsx
│ ├── llms.ts
│ ├── mdx.ts
│ ├── postbuild.ts
│ ├── rehype
│ │ ├── display-shiki-notation.ts
│ │ └── inline-shiki.ts
│ ├── remark
│ │ ├── authors.ts
│ │ ├── blog-posts.ts
│ │ ├── callout.ts
│ │ ├── code-group.ts
│ │ ├── code.ts
│ │ ├── details.ts
│ │ ├── filename.ts
│ │ ├── inferred-frontmatter.ts
│ │ ├── links.ts
│ │ ├── sponsors.ts
│ │ ├── steps.ts
│ │ ├── strong-block.ts
│ │ ├── subheading.ts
│ │ └── twoslash.ts
│ ├── resolve-vocs-modules.ts
│ ├── search.ts
│ ├── shiki
│ │ ├── transformerEmptyLine.ts
│ │ ├── transformerLineNumbers.ts
│ │ ├── transformerNotationInclude.ts
│ │ ├── transformerShrinkIndent.ts
│ │ ├── transformerSplitIdentifiers.ts
│ │ ├── transformerTagLine.ts
│ │ ├── transformerTitle.ts
│ │ ├── twoslashRenderer.ts
│ │ ├── twoslasher.ts
│ │ └── utils.ts
│ ├── virtual-blog.ts
│ ├── virtual-config.ts
│ ├── virtual-consumer-components.ts
│ ├── virtual-routes.ts
│ └── virtual-styles.ts
│ ├── prerender.tsx
│ ├── preview.ts
│ ├── utils
│ ├── cache.ts
│ ├── getGitTimestamp.ts
│ ├── hash.ts
│ ├── html.tsx
│ ├── resolveOutDir.ts
│ ├── resolveVocsConfig.ts
│ ├── search.ts
│ ├── serveStatic.ts
│ ├── slash.ts
│ └── vercel.ts
│ └── vite.config.ts
├── tsconfig.base.json
├── tsconfig.build.json
├── tsconfig.json
├── vercel.json
└── vocs.config.tsx
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.1.0/schema.json",
3 | "changelog": [
4 | "@changesets/changelog-github",
5 | {
6 | "repo": "wagmi-dev/vocs"
7 | }
8 | ],
9 | "commit": false,
10 | "access": "public",
11 | "baseBranch": "main",
12 | "updateInternalDependencies": "patch",
13 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
14 | "onlyUpdatePeerDependentsWhenOutOfRange": true
15 | },
16 | "ignore": ["playgrounds-*"]
17 | }
18 |
--------------------------------------------------------------------------------
/.github/actions/install-dependencies/action.yml:
--------------------------------------------------------------------------------
1 | name: "Install dependencies"
2 | description: "Prepare repository and all dependencies"
3 |
4 | runs:
5 | using: "composite"
6 | steps:
7 | - name: Set up pnpm
8 | uses: wevm/actions/.github/actions/pnpm@main
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'npm'
4 | directory: '/'
5 | schedule:
6 | interval: 'monthly'
7 | open-pull-requests-limit: 0
8 |
9 | - package-ecosystem: 'github-actions'
10 | directory: '/'
11 | schedule:
12 | interval: 'monthly'
13 |
--------------------------------------------------------------------------------
/.github/workflows/canary.yml:
--------------------------------------------------------------------------------
1 | name: Release (Canary)
2 | on:
3 | push:
4 | branches: [main]
5 | workflow_dispatch:
6 |
7 | jobs:
8 | canary:
9 | name: Release canary
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 5
12 |
13 | steps:
14 | - name: Clone repository
15 | uses: actions/checkout@v4
16 |
17 | - name: Install dependencies
18 | uses: ./.github/actions/install-dependencies
19 |
20 | - name: Setup .npmrc file
21 | uses: actions/setup-node@v4
22 | with:
23 | registry-url: 'https://registry.npmjs.org'
24 |
25 | - name: Set version
26 | run: |
27 | jq --arg prop "workspaces" 'del(.[$prop])' package.json > package.tmp.json && rm package.json && cp package.tmp.json package.json && rm package.tmp.json
28 | cd src
29 | npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S')
30 | cd ../create-vocs
31 | npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S')
32 |
33 | - name: Build
34 | run: pnpm build
35 |
36 | - name: Publish to npm
37 | run: |
38 | pnpx tsx ./scripts/prepublish.ts
39 | cd src
40 | npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//')
41 | cd ../create-vocs
42 | sed -i -e 's/workspace:\*/main/g' package.json
43 | npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//')
44 | env:
45 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/on-pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Pull request
2 | on:
3 | pull_request:
4 | types: [opened, reopened, synchronize, ready_for_review]
5 |
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
8 | cancel-in-progress: true
9 |
10 | jobs:
11 | verify:
12 | name: Verify
13 | uses: ./.github/workflows/verify.yml
14 | secrets: inherit
--------------------------------------------------------------------------------
/.github/workflows/on-push-to-main.yml:
--------------------------------------------------------------------------------
1 | name: Main
2 | on:
3 | push:
4 | branches: [main]
5 | workflow_dispatch:
6 |
7 | concurrency:
8 | group: ${{ github.workflow }}-${{ github.ref }}
9 | cancel-in-progress: true
10 |
11 | jobs:
12 | verify:
13 | name: Verify
14 | uses: ./.github/workflows/verify.yml
15 | secrets: inherit
--------------------------------------------------------------------------------
/.github/workflows/prune-tags.yml:
--------------------------------------------------------------------------------
1 | name: Prune NPM tags
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '0 0 * * *'
6 |
7 | jobs:
8 | prune:
9 | name: Prune NPM tags
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Clone repository
13 | uses: actions/checkout@v4
14 |
15 | - name: Setup .npmrc file
16 | uses: actions/setup-node@v4
17 | with:
18 | registry-url: 'https://registry.npmjs.org'
19 |
20 | - name: Prune tags (vocs)
21 | run: cd src && npm view --json | jq -r '.["dist-tags"] | to_entries | .[] | select(.key != "latest") | select(.key != "main") | select(.key != "alpha") | select(.key != "next") | .key' | xargs -I % npm dist-tag rm vocs %
22 | env:
23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24 |
25 | - name: Prune tags (create-vocs)
26 | run: cd create-vocs && npm view --json | jq -r '.["dist-tags"] | to_entries | .[] | select(.key != "latest") | select(.key != "main") | select(.key != "alpha") | select(.key != "next") | .key' | xargs -I % npm dist-tag rm create-vocs %
27 | env:
28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/verify.yml:
--------------------------------------------------------------------------------
1 | name: Verify
2 | on:
3 | workflow_call:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | check:
8 | name: Check
9 | runs-on: ubuntu-latest
10 | timeout-minutes: 5
11 |
12 | steps:
13 | - name: Clone repository
14 | uses: actions/checkout@v4
15 |
16 | - name: Install dependencies
17 | uses: ./.github/actions/install-dependencies
18 |
19 | - name: Check code
20 | run: pnpm check
21 |
22 | - uses: stefanzweifel/git-auto-commit-action@v5
23 | env:
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | with:
26 | commit_message: 'chore: format'
27 | commit_user_name: 'github-actions[bot]'
28 | commit_user_email: 'github-actions[bot]@users.noreply.github.com'
29 |
30 | build:
31 | name: Build
32 | runs-on: ubuntu-latest
33 | timeout-minutes: 5
34 |
35 | steps:
36 | - name: Clone repository
37 | uses: actions/checkout@v4
38 |
39 | - name: Install dependencies
40 | uses: ./.github/actions/install-dependencies
41 |
42 | - name: Build
43 | run: pnpm build
44 |
45 | types:
46 | name: Check types
47 | runs-on: ubuntu-latest
48 |
49 | steps:
50 | - name: Clone repository
51 | uses: actions/checkout@v4
52 |
53 | - name: Install dependencies
54 | uses: ./.github/actions/install-dependencies
55 |
56 | - name: Check
57 | run: pnpm check:types
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | dist
3 | node_modules
4 | _lib
5 | tsconfig.tsbuildinfo
6 | tsconfig.*.tsbuildinfo
7 | vocs.config.ts.timestamp-*
8 | .vercel
9 | .vocs
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["biomejs.biome", "vunguyentuan.vscode-css-variables"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "biomejs.biome",
3 | "typescript.tsdk": "node_modules/typescript/lib",
4 | "typescript.enablePromptUseWorkspaceTsdk": true,
5 | "[json]": {
6 | "editor.defaultFormatter": "biomejs.biome"
7 | },
8 | "[javascript]": {
9 | "editor.defaultFormatter": "biomejs.biome"
10 | },
11 | "[javascriptreact]": {
12 | "editor.defaultFormatter": "biomejs.biome"
13 | },
14 | "[typescript]": {
15 | "editor.defaultFormatter": "biomejs.biome"
16 | },
17 | "[typescriptreact]": {
18 | "editor.defaultFormatter": "biomejs.biome"
19 | },
20 | "css.validate": false
21 | }
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | src/CHANGELOG.md
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | src/LICENSE
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | src/README.md
--------------------------------------------------------------------------------
/api/og.tsx:
--------------------------------------------------------------------------------
1 | import { ImageResponse } from '@vercel/og'
2 | import * as React from 'react'
3 |
4 | export const config = {
5 | runtime: 'edge',
6 | }
7 |
8 | // https://og-playground.vercel.app/?share=bVLBbtswDP0VQcPQi5O4blYEQtrD2n3BCuySiyzRslpZNCQ5mRfk30c5FdYNO4l8pN6jnnjmCjVwwffaHg-esZhmBw_nc44Z68GaPgl2c1vXn2-qK3iyOvX_YNrG0cmZ0M7Bz4Lm-NkGUMmip5pCNw2-VF-nmGw3P6FP4LOIogNCKbdSvZmAk9dP6DBQ_VNz1zTNl9JAbAt86m2CAo5Sa-sNwbt6fB_kcjn4xxzs7WBYDOrhwPuUxig2myOquNZwXIKVQ4MrLcPbOh7Ngb-_n9rviYzy4g4bZDDWv-Ao2HZHAmxzFSAX_zR19LLv9hfQMHf3eZgF-VE8bdFpwv6iKulXTAkHwVa3DbE_kkedNfsN0f9HB0epbCLz6zURfFBttovqB4Vmm-meobMemHHYSscGSFLLJOlmYDNOgWlU00CfIfO_FdXrySuOY4YjF2e-rAIXu7qu-NUrLrY50dBOhotOuggVhwFf7cs85kVLpyUjnjzot6EFzUUKE1wqnmRLHT04hycMTvPLbw
9 | export default async function handler(request: Request) {
10 | const { searchParams } = new URL(request.url)
11 |
12 | const logo = searchParams.get('logo')
13 | const title = searchParams.get('title')
14 | const description = searchParams.get('description')
15 |
16 | return new ImageResponse(
17 |
29 | {/* biome-ignore lint/a11y/useAltText: */}
30 | {logo &&

}
31 |
32 | {title}
33 |
34 | {description && (
35 |
{description}
36 | )}
37 |
,
38 | {
39 | width: 1200,
40 | height: 630,
41 | },
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 | "files": {
4 | "ignore": [
5 | "_lib",
6 | "dist",
7 | "**/node_modules",
8 | "tsconfig.json",
9 | "tsconfig.*.json",
10 | ".vercel",
11 | ".vocs"
12 | ]
13 | },
14 | "formatter": {
15 | "enabled": true,
16 | "formatWithErrors": false,
17 | "indentStyle": "space",
18 | "indentWidth": 2,
19 | "lineWidth": 100
20 | },
21 | "linter": {
22 | "enabled": true,
23 | "rules": {
24 | "style": {
25 | "noNonNullAssertion": "off"
26 | },
27 | "suspicious": {
28 | "noAssignInExpressions": "off",
29 | "noExplicitAny": "off",
30 | "noArrayIndexKey": "off"
31 | }
32 | }
33 | },
34 | "javascript": {
35 | "formatter": {
36 | "quoteStyle": "single",
37 | "trailingCommas": "all",
38 | "semicolons": "asNeeded"
39 | }
40 | },
41 | "organizeImports": {
42 | "enabled": true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/create-vocs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # create-vocs
2 |
3 | ## 1.0.0-alpha.5
4 |
5 | ### Patch Changes
6 |
7 | - [`7815f60`](https://github.com/wevm/vocs/commit/7815f60a3f89bdc991383aed42cd04e19e3e0db7) Thanks [@jxom](https://github.com/jxom)! - Added `typescript` & `@types/react` as dependencies.
8 |
9 | ## 1.0.0-alpha.4
10 |
11 | ### Patch Changes
12 |
13 | - [`957d228`](https://github.com/wevm/vocs/commit/957d228dc2723a63e302374f780e2d26b4c73aff) Thanks [@jxom](https://github.com/jxom)! - Fixed \`.gitignore\` dist directory.
14 |
15 | ## 1.0.0-alpha.3
16 |
17 | ### Patch Changes
18 |
19 | - [`28d09f1`](https://github.com/wevm/vocs/commit/28d09f13e629562f2627f7d6c2cddcc64d6834de) Thanks [@jxom](https://github.com/jxom)! - Fixed top nav styling.
20 |
21 | ## 1.0.0-alpha.2
22 |
23 | ### Patch Changes
24 |
25 | - [`3e717c5`](https://github.com/wevm/vocs/commit/3e717c5288c2d58b37970d64bc57a868f72b6741) Thanks [@jxom](https://github.com/jxom)! - Modified template to include landing page.
26 |
27 | ## 1.0.0-alpha.1
28 |
29 | ### Patch Changes
30 |
31 | - Initial release
32 |
33 | ## 1.0.0-alpha.0
34 |
35 | ### Major Changes
36 |
37 | - [`4226240`](https://github.com/wevm/vocs/commit/4226240f0e70fefb2059cb599bda478bb8eb268c) Thanks [@jxom](https://github.com/jxom)! - Initial release
38 |
--------------------------------------------------------------------------------
/create-vocs/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { createRequire } from 'node:module'
3 | import { cac } from 'cac'
4 | import { type InitParameters, init } from './init.js'
5 |
6 | const require = createRequire(import.meta.url)
7 | const pkg = require('../package.json')
8 |
9 | const cli = cac('create-vocs')
10 |
11 | cli.usage('[options]').option('-n, --name [name]', 'Name of project')
12 |
13 | cli.help()
14 | cli.version(pkg.version)
15 |
16 | const { options } = cli.parse()
17 |
18 | if (!options.help) init(options as InitParameters)
19 |
--------------------------------------------------------------------------------
/create-vocs/index.ts:
--------------------------------------------------------------------------------
1 | export { init, type InitParameters } from './init.js'
2 |
--------------------------------------------------------------------------------
/create-vocs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-vocs",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "bin": {
6 | "create-vocs": "./_lib/bin.js"
7 | },
8 | "main": "./_lib/index.js",
9 | "exports": {
10 | ".": "./_lib/index.js"
11 | },
12 | "dependencies": {
13 | "@clack/prompts": "^0.7.0",
14 | "cac": "^6.7.14",
15 | "detect-package-manager": "^3.0.2",
16 | "fs-extra": "^11.3.0",
17 | "picocolors": "^1.1.1"
18 | },
19 | "repository": "wevm/vocs"
20 | }
21 |
--------------------------------------------------------------------------------
/create-vocs/templates/default/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # production
9 | /docs/dist
10 |
11 | # misc
12 | .DS_Store
13 | *.pem
14 |
15 | # debug
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 |
20 | # typescript
21 | *.tsbuildinfo
22 |
--------------------------------------------------------------------------------
/create-vocs/templates/default/README.md:
--------------------------------------------------------------------------------
1 | This is a [Vocs](https://vocs.dev) project bootstrapped with the Vocs CLI.
2 |
--------------------------------------------------------------------------------
/create-vocs/templates/default/docs/pages/example.mdx:
--------------------------------------------------------------------------------
1 | # Example
2 |
3 | This is an example page.
--------------------------------------------------------------------------------
/create-vocs/templates/default/docs/pages/getting-started.mdx:
--------------------------------------------------------------------------------
1 | # Get started
2 |
3 | Hello world!
--------------------------------------------------------------------------------
/create-vocs/templates/default/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | layout: landing
3 | ---
4 |
5 | import { HomePage } from 'vocs/components'
6 |
7 |
8 |
9 | My Awesome Docs
10 |
11 | This is a description of my documentation website.
12 |
13 | Get started
14 | GitHub
15 |
16 |
--------------------------------------------------------------------------------
/create-vocs/templates/default/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Docs",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vocs dev",
8 | "build": "vocs build",
9 | "preview": "vocs preview"
10 | },
11 | "dependencies": {
12 | "react": "latest",
13 | "react-dom": "latest",
14 | "vocs": "latest"
15 | },
16 | "devDependencies": {
17 | "@types/react": "latest",
18 | "typescript": "latest"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/create-vocs/templates/default/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["**/*.ts", "**/*.tsx"]
24 | }
25 |
--------------------------------------------------------------------------------
/create-vocs/templates/default/vocs.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vocs'
2 |
3 | export default defineConfig({
4 | title: 'Docs',
5 | sidebar: [
6 | {
7 | text: 'Getting Started',
8 | link: '/getting-started',
9 | },
10 | {
11 | text: 'Example',
12 | link: '/example',
13 | },
14 | ],
15 | })
16 |
--------------------------------------------------------------------------------
/create-vocs/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
3 | "extends": "../tsconfig.base.json",
4 | "exclude": ["_lib", "node_modules"],
5 | "include": ["*"],
6 | "compilerOptions": {
7 | "declaration": true,
8 | "declarationMap": true,
9 | "outDir": "./_lib",
10 | "sourceMap": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/playgrounds/config-json/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | # Hello world
--------------------------------------------------------------------------------
/playgrounds/config-json/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playgrounds-default",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "node --import tsx/esm ../../src/cli/index.ts dev",
8 | "build": "NODE_ENV=production node --import tsx/esm ../../src/cli/index.ts build",
9 | "preview": "node --import tsx/esm ../../src/cli/index.ts preview",
10 | "dist:dev": "vocs dev",
11 | "dist:build": "NODE_ENV=production vocs build",
12 | "dist:preview": "vocs preview"
13 | },
14 | "dependencies": {
15 | "react": "catalog:",
16 | "react-dom": "catalog:",
17 | "vocs": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/playgrounds/config-json/vocs.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "sidebar": [
3 | {
4 | "text": "Getting Started",
5 | "link": "/"
6 | },
7 | {
8 | "text": "Migration Guide",
9 | "link": "/migration-guide"
10 | }
11 | ],
12 | "title": "JSON Config Example"
13 | }
14 |
--------------------------------------------------------------------------------
/playgrounds/config-toml/Vocs.toml:
--------------------------------------------------------------------------------
1 | theme = { accent-color = "red" }
2 |
3 | title = "TOML Config Example"
4 |
5 | sidebar = [
6 | { text = "Getting Started", link = "/" },
7 | { text = "Migration Guide", link = "/migration-guide" }
8 | ]
9 |
10 | [[top-nav]]
11 | text = "Home"
12 | link = "/"
--------------------------------------------------------------------------------
/playgrounds/config-toml/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | # Hello world
--------------------------------------------------------------------------------
/playgrounds/config-toml/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playgrounds-default",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "node --import tsx/esm ../../src/cli/index.ts dev",
8 | "build": "NODE_ENV=production node --import tsx/esm ../../src/cli/index.ts build",
9 | "preview": "node --import tsx/esm ../../src/cli/index.ts preview",
10 | "dist:dev": "vocs dev",
11 | "dist:build": "NODE_ENV=production vocs build",
12 | "dist:preview": "vocs preview"
13 | },
14 | "dependencies": {
15 | "react": "catalog:",
16 | "react-dom": "catalog:",
17 | "vocs": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/playgrounds/custom-layout/docs/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function Layout({ children }: { children: React.ReactNode }) {
2 | return {children}
3 | }
4 |
5 | export function TopNavEnd() {
6 | return TopNavEnd
7 | }
8 |
--------------------------------------------------------------------------------
/playgrounds/custom-layout/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | Example overview page with custome layout.
4 |
5 | ## Example Section
6 |
7 | Hello there.
--------------------------------------------------------------------------------
/playgrounds/custom-layout/docs/pages/subpage.mdx:
--------------------------------------------------------------------------------
1 | # Subpage
2 |
3 | Here is an example of a subpage.
4 |
5 | ## Example Section
6 |
7 | Hello there.
--------------------------------------------------------------------------------
/playgrounds/custom-layout/docs/styles.css:
--------------------------------------------------------------------------------
1 | .custom_fullwidth .vocs_DocsLayout_gutterLeft {
2 | justify-content: normal;
3 | width: auto;
4 | }
5 |
6 | @media screen and (width > 1080px) {
7 | .custom_fullwidth .vocs_DocsLayout_gutterTop_offsetLeftGutter {
8 | padding-left: max(var(--vocs-sidebar_width), calc(1.5rem + var(--vocs-sidebar_width)));
9 | padding-right: 1.5rem;
10 | }
11 |
12 | .custom_fullwidth .vocs_DocsLayout_content_withSidebar {
13 | margin-left: max(var(--vocs-sidebar_width), calc(1.5rem + var(--vocs-sidebar_width)));
14 | }
15 |
16 | .vocs_Content,
17 | .vocs_Footer {
18 | max-width: 70ch;
19 | padding-left: 0px;
20 | padding-right: 0px;
21 | }
22 | }
23 |
24 | .custom_fullwidth .vocs_DocsLayout_gutterTop_offsetLeftGutter .vocs_DesktopTopNav {
25 | padding: 0;
26 | }
27 |
--------------------------------------------------------------------------------
/playgrounds/custom-layout/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playgrounds-custom-layout",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "node --import tsx/esm ../../src/cli/index.ts dev",
8 | "build": "NODE_ENV=production node --import tsx/esm ../../src/cli/index.ts build",
9 | "preview": "node --import tsx/esm ../../src/cli/index.ts preview",
10 | "dist:dev": "vocs dev",
11 | "dist:build": "NODE_ENV=production vocs build",
12 | "dist:preview": "vocs preview"
13 | },
14 | "dependencies": {
15 | "react": "catalog:",
16 | "react-dom": "catalog:",
17 | "vocs": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/playgrounds/custom-layout/vocs.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from '../../src/index.js'
2 |
3 | export default defineConfig({
4 | sidebar: [
5 | {
6 | text: 'Overview',
7 | link: '/',
8 | },
9 | {
10 | text: 'Subpage',
11 | link: '/subpage',
12 | },
13 | ],
14 | title: 'Custom Layout',
15 | topNav: [
16 | {
17 | text: 'Overview',
18 | link: '/',
19 | },
20 | {
21 | text: 'Subpage',
22 | link: '/subpage',
23 | },
24 | ],
25 | })
26 |
--------------------------------------------------------------------------------
/playgrounds/default/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | layout: minimal
3 | showLogo: false
4 | ---
5 |
6 | import { HomePage } from 'vocs/components'
7 |
8 |
9 |
10 |
11 |
12 | Documentation Framework powered by Vite and React
13 |
14 | Vocs is a minimalistic documentation generator designed to supercharge your documentation workflow, built with modern web technologies.
15 |
16 | Get started
17 | GitHub
18 |
19 |
20 |
--------------------------------------------------------------------------------
/playgrounds/default/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playgrounds-default",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "node --import tsx/esm ../../src/cli/index.ts dev",
8 | "build": "NODE_ENV=production node --import tsx/esm ../../src/cli/index.ts build",
9 | "preview": "node --import tsx/esm ../../src/cli/index.ts preview",
10 | "dist:dev": "vocs dev",
11 | "dist:build": "NODE_ENV=production vocs build",
12 | "dist:preview": "vocs preview"
13 | },
14 | "dependencies": {
15 | "react": "catalog:",
16 | "react-dom": "catalog:",
17 | "vocs": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/playgrounds/default/vocs.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from '../../src/index.js'
2 |
3 | export default defineConfig({
4 | sidebar: [
5 | {
6 | text: 'Introduction',
7 | },
8 | ],
9 | theme: {
10 | accentColor: 'red',
11 | },
12 | title: 'Awesome Docs',
13 | })
14 |
--------------------------------------------------------------------------------
/playgrounds/tailwind/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Tailwind
3 | ---
4 |
5 | # Tailwind
6 |
7 |
8 | Hello world
9 |
--------------------------------------------------------------------------------
/playgrounds/tailwind/docs/styles.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
--------------------------------------------------------------------------------
/playgrounds/tailwind/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playgrounds-tailwind",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "node --import tsx/esm ../../src/cli/index.ts dev",
8 | "build": "NODE_ENV=production node --import tsx/esm ../../src/cli/index.ts build",
9 | "preview": "node --import tsx/esm ../../src/cli/index.ts preview"
10 | },
11 | "dependencies": {
12 | "react": "catalog:",
13 | "react-dom": "catalog:"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'create-vocs'
3 | - 'og'
4 | - 'playgrounds/*'
5 | - 'site'
6 | - 'src'
7 |
8 | catalog:
9 | '@types/react': ^19
10 | '@types/react-dom': ^19
11 | react: ^19
12 | react-dom: ^19
13 | typescript: ^5.7.3
--------------------------------------------------------------------------------
/scripts/postbuild.ts:
--------------------------------------------------------------------------------
1 | // TODO: Probably don't do this?
2 |
3 | import { readdirSync } from 'node:fs'
4 | import { resolve } from 'node:path'
5 | import { default as fs } from 'fs-extra'
6 |
7 | // Copy index.html
8 | fs.copyFileSync(
9 | resolve(import.meta.dirname, '../src/vite/index.html'),
10 | resolve(import.meta.dirname, '../src/_lib/vite/index.html'),
11 | )
12 |
13 | // Copy public folder
14 | fs.copy(
15 | resolve(import.meta.dirname, '../src/app/public'),
16 | resolve(import.meta.dirname, '../src/_lib/app/public'),
17 | )
18 |
19 | // Copy create-vocs templates
20 | fs.copy(
21 | resolve(import.meta.dirname, '../create-vocs/templates'),
22 | resolve(import.meta.dirname, '../create-vocs/_lib/templates'),
23 | )
24 |
25 | rewriteExtensions(resolve(import.meta.dirname, '../src/_lib'))
26 | rewriteMdxPlugin()
27 |
28 | ////////////////////////////////////////////////////////////////////
29 |
30 | function rewriteExtensions(dir: string) {
31 | const files = readdirSync(dir)
32 | for (const file of files) {
33 | const path = resolve(dir, file)
34 | if (isDir(path)) {
35 | rewriteExtensions(path)
36 | continue
37 | }
38 | if (path.endsWith('.map')) continue
39 | if (path.endsWith('root.js')) continue
40 | if (path.endsWith('vocs-config.js')) continue
41 | if (path.endsWith('virtual-consumer-components.js')) continue
42 | const fileContent = fs.readFileSync(path, 'utf-8')
43 | fs.writeFileSync(path, fileContent.replace(/\.(tsx|ts)/g, '.js'))
44 | }
45 | }
46 |
47 | function rewriteMdxPlugin() {
48 | const path = resolve(import.meta.dirname, '../src/_lib/vite/plugins/mdx.js')
49 | const content = fs.readFileSync(path, 'utf-8')
50 | fs.writeFileSync(path, content.replace('@mdx-js/react', 'vocs/mdx-react'))
51 | }
52 |
53 | function isDir(dir: string) {
54 | try {
55 | readdirSync(dir)
56 | return true
57 | } catch {
58 | return false
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/scripts/prepublish.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'node:path'
2 | import { default as fs } from 'fs-extra'
3 |
4 | // Writes the current package.json version to `./src/cli/version.ts`.
5 | const versionFilePath = join(import.meta.dirname, '../src/cli/version.ts')
6 | const packageJsonPath = join(import.meta.dirname, '../src/package.json')
7 | const packageVersion = fs.readJsonSync(packageJsonPath).version
8 |
9 | fs.writeFileSync(versionFilePath, `export const version = '${packageVersion}'\n`)
10 |
--------------------------------------------------------------------------------
/site/components/Example.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function Example() {
4 | return Hello world.
5 | }
6 |
--------------------------------------------------------------------------------
/site/components/MyButton.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 | import type * as React from 'react'
3 | import { Button } from 'vocs/components'
4 |
5 | type ButtonProps = {
6 | children: React.ReactNode
7 | className?: string
8 | href?: string
9 | variant?: 'accent'
10 | }
11 |
12 | export function Large({ children, className, ...rest }: ButtonProps) {
13 | return (
14 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/site/footer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export default function Footer() {
4 | return (
5 |
6 |
© {new Date().getFullYear()} weth, LLC.
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/site/pages/blank.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | showOutline: false
3 | showSidebar: false
4 | ---
5 |
6 | # Blank page
7 |
8 | This is a blank page.
9 |
10 | ```ts
11 | const a = 1 // [\!code --]
12 | const b = 2 // [\!code ++]
13 | const c = 3
14 | ```
15 |
--------------------------------------------------------------------------------
/site/pages/blog/gm.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | layout: minimal
3 | date: 2023-11-25
4 | ---
5 |
6 | # gm
7 |
8 | ::authors
9 |
10 | we're so back.
--------------------------------------------------------------------------------
/site/pages/blog/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | layout: minimal
3 | ---
4 |
5 | # Blog
6 |
7 | ::blog-posts
--------------------------------------------------------------------------------
/site/pages/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Vocs – React Documentation Framework
3 | layout: landing
4 | showLogo: false
5 | ---
6 |
7 | import { HomePage } from '../../src/components'
8 |
9 |
10 |
11 | Minimal Documentation Framework, powered by React + Vite.
12 |
13 | Vocs is a minimal static documentation generator designed to supercharge your documentation workflow, built with modern web technologies.
14 |
15 | Get started
16 | GitHub
17 |
18 |
19 |
20 | # Sponsors
21 |
22 | ::sponsors
23 |
24 |
--------------------------------------------------------------------------------
/site/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wevm/vocs/ee0179c47147a5fe161c7f758eb50d8168892d1d/site/public/og.png
--------------------------------------------------------------------------------
/site/public/vocs-icon-dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/site/public/vocs-icon-light.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/site/snippets/example.ts:
--------------------------------------------------------------------------------
1 | // [!region import]
2 | import { http, createPublicClient } from 'viem'
3 | import { mainnet } from 'viem/chains'
4 | // [!endregion import]
5 |
6 | // [!region setup]
7 | const client = createPublicClient({
8 | chain: mainnet,
9 | transport: http(),
10 | })
11 | // [!endregion setup]
12 |
13 | // [!region usage-1]
14 | const blockNumber_$1 = await client.getBlockNumber()
15 | // [!endregion usage-1]
16 |
17 | // [!region usage-2-docs]
18 | const blockNumber_$2 = await client.getBlockNumber() // [\!code hl] // [!code focus]
19 | // [!endregion usage-2-docs]
20 |
21 | // [!region usage-2]
22 | const blockNumber_$3 = await client.getBlockNumber() // [!code hl]
23 | // [!endregion usage-2]
24 |
25 | // [!region usage-3-docs]
26 | // [\!code word:getBlockNumber] // [!code focus]
27 | const blockNumber_$4 = await client.getBlockNumber()
28 | // [!endregion usage-3-docs]
29 |
30 | // [!region usage-3]
31 | // [!code word:getBlockNumber]
32 | const blockNumber_$5 = await client.getBlockNumber()
33 | // [!endregion usage-3]
34 |
35 | // [!region usage-4]
36 | const blockNumber_$6 = await client.getBlockNumber()
37 | // ^?
38 | // [!endregion usage-4]
39 |
--------------------------------------------------------------------------------
/site/styles.css:
--------------------------------------------------------------------------------
1 | .vocs_NavLogo_logoImage {
2 | height: 30% !important;
3 | }
4 |
5 | .footer {
6 | align-items: center;
7 | color: var(--vocs-color_text3);
8 | display: flex;
9 | flex-direction: column;
10 | font-size: 14px;
11 | line-height: 1.5em;
12 | width: 100%;
13 | }
14 |
15 | .vocs_Button_button.text-xl {
16 | font-size: var(--vocs-fontSize_32);
17 | height: var(--vocs-fontSize_64);
18 | }
19 |
--------------------------------------------------------------------------------
/src/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024-present weth, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/app/components/Authors.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, primitiveColorVars } from '../styles/vars.css.js'
3 |
4 | export const root = style({
5 | color: primitiveColorVars.text3,
6 | fontSize: fontSizeVars[14],
7 | })
8 |
9 | export const authors = style(
10 | {
11 | color: primitiveColorVars.text,
12 | },
13 | 'authors',
14 | )
15 |
16 | export const link = style(
17 | {
18 | textDecoration: 'underline',
19 | textUnderlineOffset: '2px',
20 | ':hover': {
21 | color: primitiveColorVars.text2,
22 | },
23 | },
24 | 'link',
25 | )
26 |
27 | export const separator = style(
28 | {
29 | color: primitiveColorVars.text3,
30 | },
31 | 'separator',
32 | )
33 |
--------------------------------------------------------------------------------
/src/app/components/Banner.tsx:
--------------------------------------------------------------------------------
1 | import { runSync } from '@mdx-js/mdx'
2 | import { Cross1Icon } from '@radix-ui/react-icons'
3 | import { assignInlineVars } from '@vanilla-extract/dynamic'
4 | import clsx from 'clsx'
5 | import { Fragment, useMemo } from 'react'
6 | import * as runtime from 'react/jsx-runtime'
7 |
8 | import { useConfig } from '../hooks/useConfig.js'
9 | import { deserializeElement } from '../utils/deserializeElement.js'
10 | import * as styles from './Banner.css.js'
11 |
12 | export type BannerProps = {
13 | hide?: () => void
14 | }
15 |
16 | export function Banner({ hide }: BannerProps) {
17 | const { banner } = useConfig()
18 | const ConsumerBanner = useMemo(() => {
19 | const content = banner?.content ?? ''
20 | if (!content) return null
21 | if (typeof content !== 'string') return () => deserializeElement(content)
22 | const { default: MDXBanner } = runSync(content, { ...runtime, Fragment } as never)
23 | return MDXBanner
24 | }, [banner])
25 |
26 | if (!ConsumerBanner) return null
27 | return (
28 |
35 |
36 |
37 |
38 |
39 | {banner?.dismissable !== ('false' as unknown as boolean) && (
40 |
43 | )}
44 |
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/components/BlogPosts.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, fontWeightVars, primitiveColorVars, spaceVars } from '../styles/vars.css.js'
3 |
4 | export const root = style({
5 | display: 'flex',
6 | flexDirection: 'column',
7 | gap: spaceVars['32'],
8 | })
9 |
10 | export const description = style(
11 | {
12 | marginTop: spaceVars['16'],
13 | },
14 | 'description',
15 | )
16 |
17 | export const divider = style(
18 | {
19 | borderColor: primitiveColorVars.background4,
20 | },
21 | 'divider',
22 | )
23 |
24 | export const post = style({}, 'post')
25 |
26 | export const readMore = style(
27 | {
28 | selectors: {
29 | [`${post}:hover &`]: {
30 | color: primitiveColorVars.textAccent,
31 | },
32 | },
33 | },
34 | 'readMore',
35 | )
36 |
37 | export const title = style(
38 | {
39 | color: primitiveColorVars.heading,
40 | fontSize: fontSizeVars.h2,
41 | fontWeight: fontWeightVars.semibold,
42 | },
43 | 'title',
44 | )
45 |
--------------------------------------------------------------------------------
/src/app/components/BlogPosts.tsx:
--------------------------------------------------------------------------------
1 | import { posts } from 'virtual:blog'
2 | import { Fragment } from 'react'
3 |
4 | import { Authors } from './Authors.js'
5 | import * as styles from './BlogPosts.css.js'
6 | import { RouterLink } from './RouterLink.js'
7 |
8 | export function BlogPosts() {
9 | return (
10 |
11 | {posts.map((post, index) => (
12 |
13 |
14 |
15 | {post.title}
16 |
17 |
18 | {post.description} [→]
19 |
20 |
21 |
22 | {index < posts.length - 1 &&
}
23 |
24 | ))}
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/components/Button.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import {
3 | borderRadiusVars,
4 | fontSizeVars,
5 | fontWeightVars,
6 | primitiveColorVars,
7 | spaceVars,
8 | } from '../styles/vars.css.js'
9 |
10 | export const button = style(
11 | {
12 | alignItems: 'center',
13 | background: primitiveColorVars.background3,
14 | border: `1px solid ${primitiveColorVars.border}`,
15 | borderRadius: borderRadiusVars.round,
16 | color: primitiveColorVars.text,
17 | display: 'flex',
18 | fontSize: fontSizeVars['14'],
19 | fontWeight: fontWeightVars.medium,
20 | height: '40px',
21 | padding: `0 ${spaceVars['18']}`,
22 | transition: 'background 0.1s',
23 | selectors: {
24 | '&:hover': {
25 | background: primitiveColorVars.background4,
26 | },
27 | },
28 | whiteSpace: 'pre',
29 | width: 'fit-content',
30 | },
31 | 'button',
32 | )
33 |
34 | export const button_accent = style(
35 | {
36 | background: primitiveColorVars.backgroundAccent,
37 | border: `1px solid ${primitiveColorVars.borderAccent}`,
38 | color: primitiveColorVars.backgroundAccentText,
39 | selectors: {
40 | '&:hover': {
41 | background: primitiveColorVars.backgroundAccentHover,
42 | },
43 | },
44 | },
45 | 'button_accent',
46 | )
47 |
--------------------------------------------------------------------------------
/src/app/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 | import type { ReactNode } from 'react'
3 |
4 | import * as styles from './Button.css.js'
5 | import { Link } from './Link.js'
6 |
7 | export type ButtonProps = {
8 | children: ReactNode
9 | className?: string
10 | href?: string
11 | variant?: 'accent'
12 | }
13 |
14 | export function Button({ children, className, href, variant }: ButtonProps) {
15 | return (
16 |
21 | {children}
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/components/Callout.tsx:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import type { ReactNode } from 'react'
3 |
4 | import * as styles from './Callout.css.js'
5 | import { CheckCircle } from './icons/CheckCircle.js'
6 | import { ExclamationTriangle } from './icons/ExclamationTriangle.js'
7 | import { InfoCircled } from './icons/InfoCircled.js'
8 | import { LightningBolt } from './icons/LightningBolt.js'
9 |
10 | export type CalloutProps = {
11 | className: ClassValue
12 | children: ReactNode
13 | type: 'note' | 'info' | 'warning' | 'danger' | 'tip' | 'success'
14 | }
15 |
16 | export function Callout({ className, children, type }: CalloutProps) {
17 | return (
18 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/components/Content.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { contentVars, primitiveColorVars } from '../styles/vars.css.js'
3 |
4 | export const root = style({
5 | backgroundColor: primitiveColorVars.background,
6 | flex: 1,
7 | maxWidth: contentVars.width,
8 | padding: `${contentVars.verticalPadding} ${contentVars.horizontalPadding}`,
9 | position: 'relative',
10 | width: '100%',
11 | })
12 |
--------------------------------------------------------------------------------
/src/app/components/Content.tsx:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 |
3 | import * as styles from './Content.css.js'
4 |
5 | export function Content({
6 | children,
7 | className,
8 | }: { children: React.ReactNode; className?: ClassValue }) {
9 | return {children}
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/CopyButton.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { borderRadiusVars, primitiveColorVars, spaceVars, zIndexVars } from '../styles/vars.css.js'
4 | import { root as Pre } from './mdx/Pre.css.js'
5 |
6 | export const root = style({
7 | alignItems: 'center',
8 | backgroundColor: `color-mix(in srgb, ${primitiveColorVars.background2} 75%, transparent)`,
9 | backdropFilter: 'blur(1px)',
10 | border: `1px solid ${primitiveColorVars.border}`,
11 | borderRadius: borderRadiusVars['4'],
12 | color: primitiveColorVars.text3,
13 | display: 'flex',
14 | justifyContent: 'center',
15 | position: 'absolute',
16 | right: spaceVars['18'],
17 | top: spaceVars['18'],
18 | opacity: 0,
19 | height: '32px',
20 | width: '32px',
21 | transition: 'background-color 0.15s, opacity 0.15s',
22 | zIndex: zIndexVars.surface,
23 | selectors: {
24 | '&:hover': {
25 | backgroundColor: primitiveColorVars.background4,
26 | transition: 'background-color 0.05s',
27 | },
28 | '&:focus-visible': {
29 | backgroundColor: primitiveColorVars.background4,
30 | opacity: 1,
31 | transition: 'background-color 0.05s',
32 | },
33 | '&:hover:active': {
34 | backgroundColor: primitiveColorVars.background2,
35 | },
36 | '&[data-copied="true"]:hover:active': {
37 | backgroundColor: primitiveColorVars.background4,
38 | },
39 | [`${Pre}:hover &`]: {
40 | opacity: 1,
41 | },
42 | },
43 | })
44 |
45 | export const copied = style(
46 | {
47 | height: '12px',
48 | width: '12px',
49 | },
50 | 'copied',
51 | )
52 |
--------------------------------------------------------------------------------
/src/app/components/CopyButton.tsx:
--------------------------------------------------------------------------------
1 | import * as styles from './CopyButton.css.js'
2 | import { Icon } from './Icon.js'
3 | import { Checkmark } from './icons/Checkmark.js'
4 | import { Copy } from './icons/Copy.js'
5 |
6 | export function CopyButton({ copy, copied }: { copy: () => void; copied: boolean }) {
7 | return (
8 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/components/DesktopSearch.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import {
4 | borderRadiusVars,
5 | fontSizeVars,
6 | fontWeightVars,
7 | primitiveColorVars,
8 | spaceVars,
9 | } from '../styles/vars.css.js'
10 |
11 | export const search = style(
12 | {
13 | alignItems: 'center',
14 | backgroundColor: primitiveColorVars.background3,
15 | border: `1px solid ${primitiveColorVars.border}`,
16 | borderRadius: borderRadiusVars[8],
17 | color: primitiveColorVars.text3,
18 | display: 'flex',
19 | fontSize: fontSizeVars[14],
20 | fontWeight: fontWeightVars.regular,
21 | gap: spaceVars[6],
22 | height: spaceVars[40],
23 | maxWidth: '15.5rem',
24 | paddingLeft: spaceVars[12],
25 | paddingRight: spaceVars[12],
26 | position: 'relative',
27 | width: '100%',
28 | transition: 'color 0.1s, border-color 0.1s',
29 | selectors: {
30 | '&:hover': {
31 | color: primitiveColorVars.text,
32 | borderColor: primitiveColorVars.border2,
33 | },
34 | },
35 | },
36 | 'search',
37 | )
38 |
39 | export const searchCommand = style(
40 | {
41 | alignItems: 'center',
42 | border: `1.5px solid ${primitiveColorVars.text3}`,
43 | borderRadius: borderRadiusVars[4],
44 | color: primitiveColorVars.text3,
45 | display: 'flex',
46 | height: spaceVars[12],
47 | justifyContent: 'center',
48 | marginLeft: 'auto',
49 | marginTop: spaceVars[1],
50 | padding: spaceVars[1],
51 | width: spaceVars[12],
52 | },
53 | 'searchCommand',
54 | )
55 |
--------------------------------------------------------------------------------
/src/app/components/ExternalLink.css.ts:
--------------------------------------------------------------------------------
1 | import { createVar, style } from '@vanilla-extract/css'
2 |
3 | export const arrowColor = createVar('arrowColor')
4 | export const iconUrl = createVar('iconUrl')
5 |
6 | export const root = style({
7 | selectors: {
8 | '&::after': {
9 | backgroundColor: 'currentColor',
10 | content: '',
11 | color: arrowColor,
12 | display: 'inline-block',
13 | height: '0.5em',
14 | marginBottom: '0.1em',
15 | marginLeft: '0.325em',
16 | marginRight: '0.25em',
17 | width: '0.5em',
18 | mask: `${iconUrl} no-repeat center / contain`,
19 | },
20 | },
21 | })
22 |
--------------------------------------------------------------------------------
/src/app/components/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 |
3 | import { assignInlineVars } from '@vanilla-extract/dynamic'
4 | import { forwardRef } from 'react'
5 | import { useConfig } from '../hooks/useConfig.js'
6 | import * as styles from './ExternalLink.css.js'
7 |
8 | export type ExternalLinkProps = React.DetailedHTMLProps<
9 | React.AnchorHTMLAttributes,
10 | HTMLAnchorElement
11 | > & { hideExternalIcon?: boolean }
12 |
13 | export const ExternalLink = forwardRef(
14 | ({ className, children, hideExternalIcon, href, ...props }: ExternalLinkProps, ref) => {
15 | const { basePath } = useConfig()
16 | const assetBasePath = import.meta.env.PROD ? basePath : ''
17 | return (
18 |
32 | {children}
33 |
34 | )
35 | },
36 | )
37 |
--------------------------------------------------------------------------------
/src/app/components/Icon.css.ts:
--------------------------------------------------------------------------------
1 | import { createVar, style } from '@vanilla-extract/css'
2 |
3 | export const sizeVar = createVar('size')
4 | export const srcVar = createVar('src')
5 |
6 | export const root = style({
7 | alignItems: 'center',
8 | display: 'flex',
9 | height: sizeVar,
10 | width: sizeVar,
11 | })
12 |
--------------------------------------------------------------------------------
/src/app/components/Icon.tsx:
--------------------------------------------------------------------------------
1 | import { assignInlineVars } from '@vanilla-extract/dynamic'
2 |
3 | import clsx from 'clsx'
4 | import * as styles from './Icon.css.js'
5 |
6 | export type IconProps = {
7 | className?: string
8 | label: string
9 | icon: React.ElementType
10 | size?: string
11 | style?: React.CSSProperties
12 | }
13 |
14 | export function Icon({ className, label, icon: Icon, size, style }: IconProps) {
15 | return (
16 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/components/KeyboardShortcut.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { fontSizeVars, spaceVars, viewportVars } from '../styles/vars.css.js'
4 |
5 | export const root = style({
6 | alignItems: 'center',
7 | display: 'inline-flex',
8 | gap: spaceVars[6],
9 | fontSize: fontSizeVars[12],
10 | '@media': {
11 | [viewportVars['max-720px']]: {
12 | display: 'none',
13 | },
14 | },
15 | })
16 |
17 | export const kbdGroup = style(
18 | {
19 | alignItems: 'center',
20 | display: 'inline-flex',
21 | gap: spaceVars[3],
22 | },
23 | 'kbdGroup',
24 | )
25 |
--------------------------------------------------------------------------------
/src/app/components/KeyboardShortcut.tsx:
--------------------------------------------------------------------------------
1 | import { Kbd } from './mdx/Kbd.js'
2 |
3 | import * as styles from './KeyboardShortcut.css.js'
4 |
5 | export function KeyboardShortcut(props: {
6 | description: string
7 | keys: string[]
8 | }) {
9 | const { description, keys } = props
10 | return (
11 |
12 | {description}
13 |
14 |
15 | {keys.map((key) => (
16 | {key}
17 | ))}
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/components/Link.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import {
4 | fontWeightVars,
5 | primitiveColorVars,
6 | semanticColorVars,
7 | spaceVars,
8 | } from '../styles/vars.css.js'
9 | import { arrowColor } from './ExternalLink.css.js'
10 |
11 | export const root = style({})
12 |
13 | export const accent = style(
14 | {
15 | color: semanticColorVars.link,
16 | fontWeight: fontWeightVars.medium,
17 | textUnderlineOffset: spaceVars['2'],
18 | textDecoration: 'underline',
19 | transition: 'color 0.1s',
20 | selectors: {
21 | '&:hover': {
22 | color: semanticColorVars.linkHover,
23 | },
24 | },
25 | },
26 | 'accent',
27 | )
28 |
29 | export const styleless = style(
30 | {
31 | vars: {
32 | [arrowColor]: primitiveColorVars.text3,
33 | },
34 | },
35 | 'styleless',
36 | )
37 |
--------------------------------------------------------------------------------
/src/app/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import { forwardRef } from 'react'
3 |
4 | import { useLocation } from 'react-router'
5 | import { ExternalLink } from './ExternalLink.js'
6 | import * as styles from './Link.css.js'
7 | import { RouterLink, type RouterLinkProps } from './RouterLink.js'
8 |
9 | type LinkProps = {
10 | children: React.ReactNode
11 | className?: string
12 | hideExternalIcon?: boolean
13 | onClick?: (e: React.MouseEvent) => void
14 | href?: string
15 | variant?: 'accent' | 'styleless'
16 | }
17 |
18 | export const Link = forwardRef((props: LinkProps, ref) => {
19 | const { hideExternalIcon, href, variant = 'accent' } = props
20 |
21 | const { pathname } = useLocation()
22 |
23 | // External links
24 | if (href?.match(/^(www|https?)/))
25 | return (
26 |
37 | )
38 |
39 | // Internal links
40 | const [before, after] = (href || '').split('#')
41 | const to = `${before ? before : pathname}${after ? `#${after}` : ''}`
42 | return (
43 |
54 | )
55 | })
56 |
--------------------------------------------------------------------------------
/src/app/components/Logo.css.ts:
--------------------------------------------------------------------------------
1 | import { globalStyle, style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
5 | export const logoDark = style({}, 'logoDark')
6 | globalStyle(`:root:not(.dark) ${logoDark}`, {
7 | display: 'none',
8 | })
9 |
10 | export const logoLight = style({}, 'logoLight')
11 | globalStyle(`:root.dark ${logoLight}`, {
12 | display: 'none',
13 | })
14 |
--------------------------------------------------------------------------------
/src/app/components/Logo.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 |
3 | import { useConfig } from '../hooks/useConfig.js'
4 | import * as styles from './Logo.css.js'
5 |
6 | export function Logo({ className }: { className?: string }) {
7 | const { logoUrl } = useConfig()
8 |
9 | if (!logoUrl) return null
10 | return (
11 | <>
12 | {typeof logoUrl === 'string' ? (
13 |
14 | ) : (
15 | <>
16 |
21 |
26 | >
27 | )}
28 | >
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/components/MobileSearch.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { primitiveColorVars, spaceVars } from '../styles/vars.css.js'
4 |
5 | export const searchButton = style(
6 | {
7 | alignItems: 'center',
8 | display: 'flex',
9 | color: primitiveColorVars.text,
10 | height: spaceVars[28],
11 | justifyContent: 'center',
12 | width: spaceVars[28],
13 | },
14 | 'searchButton',
15 | )
16 |
--------------------------------------------------------------------------------
/src/app/components/MobileSearch.tsx:
--------------------------------------------------------------------------------
1 | import * as Dialog from '@radix-ui/react-dialog'
2 | import { MagnifyingGlassIcon } from '@radix-ui/react-icons'
3 | import { useState } from 'react'
4 |
5 | import * as styles from './MobileSearch.css.js'
6 | import { SearchDialog } from './SearchDialog.js'
7 |
8 | export function MobileSearch() {
9 | const [open, setOpen] = useState(false)
10 |
11 | return (
12 |
13 |
14 |
17 |
18 |
19 | setOpen(false)} />
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/components/NavLogo.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, fontWeightVars, lineHeightVars } from '../styles/vars.css.js'
3 |
4 | export const logoImage = style(
5 | {
6 | height: '50%',
7 | width: 'auto',
8 | },
9 | 'logoImage',
10 | )
11 |
12 | export const title = style(
13 | {
14 | fontSize: fontSizeVars['18'],
15 | fontWeight: fontWeightVars.semibold,
16 | lineHeight: lineHeightVars.heading,
17 | },
18 | 'title',
19 | )
20 |
--------------------------------------------------------------------------------
/src/app/components/NavLogo.tsx:
--------------------------------------------------------------------------------
1 | import { useConfig } from '../hooks/useConfig.js'
2 | import { Logo } from './Logo.js'
3 | import * as styles from './NavLogo.css.js'
4 |
5 | export function NavLogo() {
6 | const config = useConfig()
7 |
8 | if (config.logoUrl) return
9 | return {config.title}
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/NotFound.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { primitiveColorVars, spaceVars } from '../styles/vars.css.js'
3 |
4 | export const root = style({
5 | alignItems: 'center',
6 | display: 'flex',
7 | flexDirection: 'column',
8 | maxWidth: '400px',
9 | margin: '0 auto',
10 | paddingTop: spaceVars['64'],
11 | })
12 |
13 | export const divider = style(
14 | {
15 | borderColor: primitiveColorVars.border,
16 | width: '50%',
17 | },
18 | 'divider',
19 | )
20 |
--------------------------------------------------------------------------------
/src/app/components/NotFound.tsx:
--------------------------------------------------------------------------------
1 | import { spaceVars } from '../styles/vars.css.js'
2 | import { Link } from './Link.js'
3 | import * as styles from './NotFound.css.js'
4 | import { H1 } from './mdx/H1.js'
5 | import { Paragraph } from './mdx/Paragraph.js'
6 |
7 | export function NotFound() {
8 | return (
9 |
10 |
Page Not Found
11 |
12 |
13 |
14 |
The page you were looking for could not be found.
15 |
16 |
Go to Home Page
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/Popover.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { borderRadiusVars, primitiveColorVars, spaceVars, zIndexVars } from '../styles/vars.css.js'
3 |
4 | export const root = style({
5 | backgroundColor: primitiveColorVars.background,
6 | border: `1px solid ${primitiveColorVars.border}`,
7 | borderRadius: borderRadiusVars[4],
8 | margin: `0 ${spaceVars[6]}`,
9 | zIndex: zIndexVars.popover,
10 | })
11 |
--------------------------------------------------------------------------------
/src/app/components/Popover.tsx:
--------------------------------------------------------------------------------
1 | import * as Popover_ from '@radix-ui/react-popover'
2 | import type { ReactNode } from 'react'
3 |
4 | import clsx from 'clsx'
5 | import * as styles from './Popover.css.js'
6 |
7 | Popover.Root = Popover_.Root
8 | Popover.Trigger = Popover_.Trigger
9 |
10 | export function Popover({ children, className }: { children: ReactNode; className?: string }) {
11 | return (
12 |
13 |
14 | {children}
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/components/Raw.tsx:
--------------------------------------------------------------------------------
1 | import { MDXProvider } from '@mdx-js/react'
2 | import type { ReactNode } from 'react'
3 |
4 | export function Raw({ children }: { children: ReactNode }) {
5 | return {children}
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/components/RouterLink.tsx:
--------------------------------------------------------------------------------
1 | import { routes } from 'virtual:routes'
2 | import { forwardRef, useEffect } from 'react'
3 | import { useInView } from 'react-intersection-observer'
4 | import { Link, type LinkProps } from 'react-router'
5 |
6 | import { mergeRefs } from '../utils/mergeRefs.js'
7 |
8 | export type RouterLinkProps = LinkProps
9 |
10 | export const RouterLink = forwardRef((props: RouterLinkProps, ref) => {
11 | const loadRoute = () => routes.find((route) => route.path === props.to)?.lazy()
12 |
13 | const { ref: intersectionRef, inView } = useInView()
14 | // biome-ignore lint/correctness/useExhaustiveDependencies:
15 | useEffect(() => {
16 | if (inView) loadRoute()
17 | }, [inView])
18 |
19 | return
20 | })
21 |
--------------------------------------------------------------------------------
/src/app/components/SkipLink.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import {
3 | borderRadiusVars,
4 | fontSizeVars,
5 | fontWeightVars,
6 | primitiveColorVars,
7 | semanticColorVars,
8 | spaceVars,
9 | } from '../styles/vars.css.js'
10 |
11 | export const root = style({
12 | background: primitiveColorVars.background,
13 | borderRadius: borderRadiusVars['4'],
14 | color: semanticColorVars.link,
15 | fontSize: fontSizeVars['14'],
16 | fontWeight: fontWeightVars.semibold,
17 | left: spaceVars[8],
18 | padding: `${spaceVars['8']} ${spaceVars['16']}`,
19 | position: 'fixed',
20 | textDecoration: 'none',
21 | top: spaceVars[8],
22 | zIndex: 999,
23 | ':focus': {
24 | clip: 'auto',
25 | clipPath: 'none',
26 | height: 'auto',
27 | width: 'auto',
28 | },
29 | })
30 |
--------------------------------------------------------------------------------
/src/app/components/SkipLink.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 | import { useLocation } from 'react-router'
3 | import { visuallyHidden } from '../styles/utils.css.js'
4 | import * as styles from './SkipLink.css.js'
5 |
6 | export const skipLinkId = 'vocs-content'
7 |
8 | export function SkipLink() {
9 | const { pathname } = useLocation()
10 | return (
11 |
12 | Skip to content
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/components/Socials.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { primitiveColorVars, spaceVars } from '../styles/vars.css.js'
3 |
4 | export const root = style({ display: 'flex', flexDirection: 'row', gap: spaceVars[8] })
5 |
6 | export const button = style(
7 | {
8 | alignItems: 'center',
9 | display: 'flex',
10 | padding: spaceVars[4],
11 | },
12 | 'button',
13 | )
14 |
15 | export const icon = style(
16 | {
17 | color: primitiveColorVars.text3,
18 | transition: 'color 0.1s',
19 | selectors: {
20 | [`${button}:hover &`]: {
21 | color: primitiveColorVars.textHover,
22 | },
23 | },
24 | },
25 | 'icon',
26 | )
27 |
--------------------------------------------------------------------------------
/src/app/components/Sponsors.tsx:
--------------------------------------------------------------------------------
1 | import { assignInlineVars } from '@vanilla-extract/dynamic'
2 | import clsx from 'clsx'
3 | import { Fragment } from 'react'
4 | import { useConfig } from '../hooks/useConfig.js'
5 | import { Link } from './Link.js'
6 | import * as styles from './Sponsors.css.js'
7 |
8 | export function Sponsors() {
9 | const { sponsors } = useConfig()
10 | return (
11 |
12 | {sponsors?.map((sponsorSet, i) => (
13 |
14 | {sponsorSet.name}
15 | {sponsorSet.items.map((sponsorRow, i) => (
16 |
24 | {sponsorRow.map((sponsor, i) => (
25 |
32 | {sponsor?.image && (
33 |

34 | )}
35 |
36 | ))}
37 |
38 | ))}
39 |
40 | ))}
41 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/src/app/components/Step.tsx:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import type { ReactNode } from 'react'
3 |
4 | import * as styles from './Step.css.js'
5 | import { H2 } from './mdx/H2.js'
6 | import { H3 } from './mdx/H3.js'
7 | import { H4 } from './mdx/H4.js'
8 | import { H5 } from './mdx/H5.js'
9 | import { H6 } from './mdx/H6.js'
10 |
11 | export type StepProps = {
12 | children: ReactNode
13 | className?: ClassValue
14 | title: ReactNode | string
15 | titleLevel?: 2 | 3 | 4 | 5 | 6
16 | }
17 |
18 | export function Step({ children, className, title, titleLevel = 2 }: StepProps) {
19 | const Element = (() => {
20 | if (titleLevel === 2) return H2
21 | if (titleLevel === 3) return H3
22 | if (titleLevel === 4) return H4
23 | if (titleLevel === 5) return H5
24 | if (titleLevel === 6) return H6
25 | throw new Error('Invalid.')
26 | })()
27 |
28 | return (
29 |
30 | {typeof title === 'string' ?
{title} : title}
31 |
{children}
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/components/Steps.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { primitiveColorVars, spaceVars, viewportVars } from '../styles/vars.css.js'
4 |
5 | export const root = style({
6 | borderLeft: `1.5px solid ${primitiveColorVars.border}`,
7 | counterReset: 'step',
8 | paddingLeft: spaceVars['24'],
9 | marginLeft: spaceVars['12'],
10 | marginTop: spaceVars['24'],
11 | '@media': {
12 | [viewportVars['max-720px']]: {
13 | marginLeft: spaceVars['4'],
14 | },
15 | },
16 | })
17 |
--------------------------------------------------------------------------------
/src/app/components/Steps.tsx:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import type { ReactNode } from 'react'
3 |
4 | import * as styles from './Steps.css.js'
5 |
6 | export type StepsProps = {
7 | children: ReactNode
8 | className?: ClassValue
9 | }
10 |
11 | export function Steps({ children, className }: StepsProps) {
12 | return {children}
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/components/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as Tabs from '@radix-ui/react-tabs'
2 | import clsx from 'clsx'
3 |
4 | import * as styles from './Tabs.css.js'
5 |
6 | export function Root(props: Tabs.TabsProps) {
7 | return
8 | }
9 |
10 | export function List(props: Tabs.TabsListProps) {
11 | return
12 | }
13 |
14 | export function Trigger(props: Tabs.TabsTriggerProps) {
15 | return
16 | }
17 |
18 | export function Content(props: Tabs.TabsContentProps) {
19 | return
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/components/ThemeToggle.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { borderRadiusVars, primitiveColorVars, spaceVars } from '../styles/vars.css.js'
3 |
4 | export const root = style({
5 | border: `1px solid ${primitiveColorVars.border}`,
6 | borderRadius: borderRadiusVars.round,
7 | display: 'flex',
8 | gap: spaceVars['8'],
9 | padding: spaceVars['4'],
10 | })
11 |
12 | export const themeToggleButton = style(
13 | {
14 | color: primitiveColorVars.text4,
15 | selectors: {
16 | '&:hover': {
17 | color: primitiveColorVars.textHover,
18 | },
19 | '&[data-active="true"]': {
20 | color: primitiveColorVars.textHover,
21 | },
22 | },
23 | },
24 | 'themeToggleButton',
25 | )
26 |
--------------------------------------------------------------------------------
/src/app/components/ThemeToggle.tsx:
--------------------------------------------------------------------------------
1 | import { useMounted } from '../hooks/useMounted.js'
2 | import { useTheme } from '../hooks/useTheme.js'
3 | import { Icon } from './Icon.js'
4 | import * as styles from './ThemeToggle.css.js'
5 | import { Moon } from './icons/Moon.js'
6 | import { Sun } from './icons/Sun.js'
7 |
8 | export function ThemeToggle() {
9 | const { theme, setTheme } = useTheme()
10 | const mounted = useMounted()
11 | if (!mounted) return null
12 | if (!theme) return null
13 | return (
14 |
15 |
23 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/app/components/icons/ArrowDiagonal.tsx:
--------------------------------------------------------------------------------
1 | export function ArrowDiagonal() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/icons/ArrowLeft.tsx:
--------------------------------------------------------------------------------
1 | export function ArrowLeft() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/icons/ArrowRight.tsx:
--------------------------------------------------------------------------------
1 | export function ArrowRight() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/icons/CheckCircle.tsx:
--------------------------------------------------------------------------------
1 | export function CheckCircle() {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/icons/Checkmark.tsx:
--------------------------------------------------------------------------------
1 | export function Checkmark() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/icons/ChevronDown.tsx:
--------------------------------------------------------------------------------
1 | export function ChevronDown() {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/icons/ChevronRight.tsx:
--------------------------------------------------------------------------------
1 | export function ChevronRight() {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/icons/ChevronUp.tsx:
--------------------------------------------------------------------------------
1 | export function ChevronUp() {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/icons/Discord.tsx:
--------------------------------------------------------------------------------
1 | export function Discord() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/icons/ExclamationTriangle.tsx:
--------------------------------------------------------------------------------
1 | export function ExclamationTriangle() {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/icons/GitHub.tsx:
--------------------------------------------------------------------------------
1 | export function GitHub() {
2 | return (
3 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/components/icons/InfoCircled.tsx:
--------------------------------------------------------------------------------
1 | export function InfoCircled() {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/icons/LightningBolt.tsx:
--------------------------------------------------------------------------------
1 | export function LightningBolt() {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/icons/Link.tsx:
--------------------------------------------------------------------------------
1 | export function Link() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/icons/Menu.tsx:
--------------------------------------------------------------------------------
1 | export function Menu() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/icons/OpenAi.tsx:
--------------------------------------------------------------------------------
1 | export function OpenAi() {
2 | return (
3 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/icons/Telegram.tsx:
--------------------------------------------------------------------------------
1 | export function Telegram() {
2 | return (
3 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/icons/Terminal.tsx:
--------------------------------------------------------------------------------
1 | export function Terminal() {
2 | return (
3 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/components/icons/Warpcast.tsx:
--------------------------------------------------------------------------------
1 | export function Warpcast() {
2 | return (
3 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/components/icons/X.tsx:
--------------------------------------------------------------------------------
1 | export function X() {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Anchor.css.ts:
--------------------------------------------------------------------------------
1 | import { globalStyle, style } from '@vanilla-extract/css'
2 |
3 | import { fontWeightVars, semanticColorVars, spaceVars } from '../../styles/vars.css.js'
4 | import { danger, info, success, tip, warning } from '../Callout.css.js'
5 | import { root as Section } from './Section.css.js'
6 |
7 | export const root = style({
8 | color: semanticColorVars.link,
9 | fontWeight: fontWeightVars.medium,
10 | textUnderlineOffset: spaceVars['2'],
11 | textDecoration: 'underline',
12 | transition: 'color 0.1s',
13 | selectors: {
14 | [`${danger} &`]: {
15 | color: semanticColorVars.dangerText,
16 | },
17 | [`${danger} &:hover`]: {
18 | color: semanticColorVars.dangerTextHover,
19 | },
20 | [`${info} &`]: {
21 | color: semanticColorVars.infoText,
22 | },
23 | [`${info} &:hover`]: {
24 | color: semanticColorVars.infoTextHover,
25 | },
26 | [`${success} &`]: {
27 | color: semanticColorVars.successText,
28 | },
29 | [`${success} &:hover`]: {
30 | color: semanticColorVars.successTextHover,
31 | },
32 | [`${tip} &`]: {
33 | color: semanticColorVars.tipText,
34 | },
35 | [`${tip} &:hover`]: {
36 | color: semanticColorVars.tipTextHover,
37 | },
38 | [`${warning} &`]: {
39 | color: semanticColorVars.warningText,
40 | },
41 | [`${warning} &:hover`]: {
42 | color: semanticColorVars.warningTextHover,
43 | },
44 | '&:hover': {
45 | color: semanticColorVars.linkHover,
46 | },
47 | },
48 | })
49 |
50 | globalStyle(`${Section} a.data-footnote-backref`, {
51 | color: semanticColorVars.link,
52 | fontWeight: fontWeightVars.medium,
53 | textUnderlineOffset: spaceVars['2'],
54 | textDecoration: 'underline',
55 | })
56 |
57 | globalStyle(`${Section} a.data-footnote-backref:hover`, {
58 | color: semanticColorVars.linkHover,
59 | })
60 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Anchor.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { ReactNode } from 'react'
3 | import { useLocation } from 'react-router'
4 |
5 | import { Link } from '../Link.js'
6 | import * as styles from './Anchor.css.js'
7 | import { Autolink } from './Autolink.js'
8 |
9 | type AnchorProps = {
10 | children: ReactNode
11 | className?: string
12 | href?: string
13 | }
14 |
15 | export function Anchor(props: AnchorProps) {
16 | const { children, href } = props
17 | const { pathname } = useLocation()
18 |
19 | // Heading slug links
20 | if (
21 | children &&
22 | typeof children === 'object' &&
23 | 'props' in children &&
24 | (children.props as { 'data-autolink-icon'?: boolean })['data-autolink-icon']
25 | )
26 | return
27 |
28 | // ID links
29 | if (href?.match(/^#/))
30 | return (
31 |
32 | )
33 |
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Aside.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Aside.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import { Callout, type CalloutProps } from '../Callout.js'
5 | import * as styles from './Aside.css.js'
6 |
7 | export function Aside(props: DetailedHTMLProps, HTMLElement>) {
8 | const className = clsx(props.className, styles.root)
9 | if ('data-callout' in props)
10 | return (
11 |
12 | {props.children}
13 |
14 | )
15 | return
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Autolink.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { root as Heading } from './Heading.css.js'
4 |
5 | export const root = style({
6 | opacity: 0,
7 | marginTop: '0.1em',
8 | position: 'absolute',
9 | transition: 'opacity 0.1s, transform 0.1s,',
10 | transform: 'translateX(-2px) scale(0.98)',
11 | selectors: {
12 | [`${Heading}:hover &`]: {
13 | opacity: 1,
14 | transform: 'translateX(0) scale(1)',
15 | },
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Autolink.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { AnchorHTMLAttributes, DetailedHTMLProps } from 'react'
3 | import { Link } from 'react-router'
4 |
5 | import * as styles from './Autolink.css.js'
6 |
7 | export function Autolink(
8 | props: Omit, HTMLAnchorElement>, 'ref'>,
9 | ) {
10 | if (!props.href) return null
11 | return
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/components/mdx/AutolinkIcon.css.ts:
--------------------------------------------------------------------------------
1 | import { createVar, style } from '@vanilla-extract/css'
2 |
3 | import { primitiveColorVars } from '../../styles/vars.css.js'
4 | import { root as Autolink } from './Autolink.css.js'
5 |
6 | export const iconUrl = createVar('iconUrl')
7 |
8 | export const root = style({
9 | backgroundColor: primitiveColorVars.textAccent,
10 | display: 'inline-block',
11 | marginLeft: '0.25em',
12 | height: '0.8em',
13 | width: '0.8em',
14 | mask: `${iconUrl} no-repeat center / contain`,
15 | transition: 'background-color 0.1s',
16 | selectors: {
17 | [`${Autolink}:hover &`]: {
18 | backgroundColor: primitiveColorVars.textAccentHover,
19 | },
20 | },
21 | })
22 |
--------------------------------------------------------------------------------
/src/app/components/mdx/AutolinkIcon.tsx:
--------------------------------------------------------------------------------
1 | import { assignInlineVars } from '@vanilla-extract/dynamic'
2 | import { clsx } from 'clsx'
3 | import type { DetailedHTMLProps, ImgHTMLAttributes } from 'react'
4 |
5 | import { useConfig } from '../../hooks/useConfig.js'
6 | import * as styles from './AutolinkIcon.css.js'
7 |
8 | export function AutolinkIcon(
9 | props: DetailedHTMLProps, HTMLImageElement>,
10 | ) {
11 | const { basePath } = useConfig()
12 | const assetBasePath = import.meta.env.PROD ? basePath : ''
13 | return (
14 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Blockquote.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { semanticColorVars, spaceVars } from '../../styles/vars.css.js'
4 |
5 | export const root = style({
6 | borderLeft: `2px solid ${semanticColorVars.blockquoteBorder}`,
7 | paddingLeft: spaceVars['16'],
8 | marginBottom: spaceVars['16'],
9 | })
10 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Blockquote.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { BlockquoteHTMLAttributes, DetailedHTMLProps } from 'react'
3 |
4 | import * as styles from './Blockquote.css.js'
5 |
6 | export function Blockquote(
7 | props: DetailedHTMLProps, HTMLQuoteElement>,
8 | ) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Code.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import {
4 | borderRadiusVars,
5 | fontSizeVars,
6 | semanticColorVars,
7 | spaceVars,
8 | } from '../../styles/vars.css.js'
9 | import { danger, info, success, tip, warning } from '../Callout.css.js'
10 | import { root as Anchor } from './Anchor.css.js'
11 | import { root as Heading } from './Heading.css.js'
12 | import { root as Pre } from './Pre.css.js'
13 |
14 | export const root = style({
15 | transition: 'color 0.1s',
16 | selectors: {
17 | [`:not(${Pre})>&`]: {
18 | backgroundColor: semanticColorVars.codeInlineBackground,
19 | border: `1px solid ${semanticColorVars.codeInlineBorder}`,
20 | borderRadius: borderRadiusVars['4'],
21 | color: semanticColorVars.codeInlineText,
22 | fontSize: fontSizeVars.code,
23 | padding: `${spaceVars['3']} ${spaceVars['6']}`,
24 | },
25 | [`${Anchor}>&`]: {
26 | color: semanticColorVars.link,
27 | },
28 | [`${Anchor}:hover>&`]: {
29 | color: semanticColorVars.linkHover,
30 | },
31 | [`${danger} &`]: {
32 | color: semanticColorVars.dangerText,
33 | },
34 | [`${info} &`]: {
35 | color: semanticColorVars.infoText,
36 | },
37 | [`${success} &`]: {
38 | color: semanticColorVars.successText,
39 | },
40 | [`${tip} &`]: {
41 | color: semanticColorVars.tipText,
42 | },
43 | [`${warning} &`]: {
44 | color: semanticColorVars.warningText,
45 | },
46 | [`${Heading} &`]: {
47 | color: 'inherit',
48 | },
49 | '.twoslash-popup-info-hover>&': {
50 | backgroundColor: 'inherit',
51 | padding: 0,
52 | textWrap: 'wrap',
53 | },
54 | '.twoslash-popup-jsdoc &': {
55 | display: 'inline',
56 | },
57 | },
58 | })
59 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Code.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Code.css.js'
5 |
6 | export function Code(props: DetailedHTMLProps, HTMLElement>) {
7 | const children = filterEmptyLines(props.children)
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
14 |
15 | function filterEmptyLines(nodes: React.ReactNode) {
16 | if (!Array.isArray(nodes)) return nodes
17 | return nodes
18 | .map((child, index) =>
19 | child.props &&
20 | 'data-line' in child.props &&
21 | typeof child.props.children === 'string' &&
22 | child.props.children.trim() === '' &&
23 | nodes[index + 1]?.props?.className?.includes('twoslash-tag-line')
24 | ? null
25 | : child,
26 | )
27 | .filter(Boolean)
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/components/mdx/CodeBlock.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './CodeBlock.css.js'
5 |
6 | export function CodeBlock(
7 | props: DetailedHTMLProps, HTMLDivElement>,
8 | ) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/CodeGroup.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { spaceVars, viewportVars } from '../../styles/vars.css.js'
4 |
5 | export const root = style({
6 | '@media': {
7 | [viewportVars['max-720px']]: {
8 | borderRadius: 0,
9 | borderRight: 'none',
10 | borderLeft: 'none',
11 | marginLeft: `calc(-1 * ${spaceVars['16']})`,
12 | marginRight: `calc(-1 * ${spaceVars['16']})`,
13 | },
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/src/app/components/mdx/CodeGroup.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactElement } from 'react'
2 |
3 | import * as Tabs from '../Tabs.js'
4 | import * as styles from './CodeGroup.css.js'
5 |
6 | export function CodeGroup({ children }: { children: ReactElement[] }) {
7 | if (!Array.isArray(children)) return null
8 | const tabs = children.map((child_: any) => {
9 | const child = child_.props['data-title'] ? child_ : child_.props.children
10 | const { props } = child
11 | const title = (props as { 'data-title'?: string })['data-title'] as string
12 | const content = props.children as ReactElement
13 | return { title, content }
14 | })
15 | return (
16 |
17 |
18 | {tabs.map(({ title }, i) => (
19 |
20 | {title}
21 |
22 | ))}
23 |
24 | {tabs.map(({ title, content }: any, i) => {
25 | const isShiki = content.props?.children?.props?.className?.includes('shiki')
26 | return (
27 |
32 | {content}
33 |
34 | )
35 | })}
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/components/mdx/CodeTitle.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import {
3 | fontSizeVars,
4 | fontWeightVars,
5 | primitiveColorVars,
6 | semanticColorVars,
7 | spaceVars,
8 | viewportVars,
9 | } from '../../styles/vars.css.js'
10 | import { root as CodeGroup } from './CodeGroup.css.js'
11 |
12 | export const root = style({
13 | alignItems: 'center',
14 | backgroundColor: semanticColorVars.codeTitleBackground,
15 | borderBottom: `1px solid ${primitiveColorVars.border}`,
16 | color: primitiveColorVars.text3,
17 | display: 'flex',
18 | fontSize: fontSizeVars['14'],
19 | fontWeight: fontWeightVars.medium,
20 | gap: spaceVars['6'],
21 | padding: `${spaceVars['8']} ${spaceVars['24']}`,
22 | '@media': {
23 | [viewportVars['max-720px']]: {
24 | borderRadius: 0,
25 | paddingLeft: spaceVars['16'],
26 | paddingRight: spaceVars['16'],
27 | },
28 | },
29 | selectors: {
30 | [`${CodeGroup} &`]: {
31 | display: 'none',
32 | },
33 | },
34 | })
35 |
--------------------------------------------------------------------------------
/src/app/components/mdx/CodeTitle.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 |
3 | import { Icon } from '../Icon.js'
4 | import { File } from '../icons/File.js'
5 | import { Terminal } from '../icons/Terminal.js'
6 | import * as styles from './CodeTitle.css.js'
7 |
8 | export function CodeTitle({
9 | children,
10 | className,
11 | language,
12 | ...props
13 | }: { children: string; className?: string; language?: string }) {
14 | return (
15 |
16 | {language === 'bash' ? (
17 |
18 | ) : children.match(/\.(.*)$/) ? (
19 |
20 | ) : null}
21 | {children}
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Details.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { content as Callout } from '../Callout.css.js'
4 |
5 | export const root = style({
6 | selectors: {
7 | [`${Callout} > * + &`]: {
8 | marginTop: '-8px',
9 | },
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Details.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, DetailsHTMLAttributes } from 'react'
3 |
4 | import * as styles from './Details.css.js'
5 |
6 | export function Details(
7 | props: DetailedHTMLProps, HTMLDetailsElement>,
8 | ) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Div.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Div.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import { useLayout } from '../../hooks/useLayout.js'
5 | import { Authors } from '../Authors.js'
6 | import { BlogPosts } from '../BlogPosts.js'
7 | import { Sponsors } from '../Sponsors.js'
8 | import { AutolinkIcon } from './AutolinkIcon.js'
9 | import { CodeGroup } from './CodeGroup.js'
10 | import * as styles from './Div.css.js'
11 | import { Steps } from './Steps.js'
12 | import { Subtitle } from './Subtitle.js'
13 |
14 | export function Div(props: DetailedHTMLProps, HTMLDivElement>) {
15 | const { layout } = useLayout()
16 |
17 | const className = clsx(props.className, styles.root)
18 |
19 | if (props.className === 'code-group')
20 | return
21 | if ('data-authors' in props) return
22 | if ('data-blog-posts' in props) return
23 | if ('data-sponsors' in props) return
24 | if ('data-autolink-icon' in props && layout === 'docs')
25 | return
26 | if ('data-vocs-steps' in props) return
27 | if (props.role === 'doc-subtitle') return
28 | return
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Figcaption.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Figcaption.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Figcaption.css.js'
5 |
6 | export function Figcaption(props: DetailedHTMLProps, HTMLElement>) {
7 | const className = clsx(props.className, styles.root)
8 |
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Figure.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Figure.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Figure.css.js'
5 |
6 | export function Figure(props: DetailedHTMLProps, HTMLElement>) {
7 | const className = clsx(props.className, styles.root)
8 |
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Footnotes.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Footnotes.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Footnotes.css.js'
5 |
6 | export function Footnotes(props: DetailedHTMLProps, HTMLElement>) {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H1.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { fontSizeVars } from '../../styles/vars.css.js'
4 |
5 | export const root = style({
6 | fontSize: fontSizeVars.h1,
7 | letterSpacing: '-0.02em',
8 | })
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H1.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './H1.css.js'
5 | import { Heading } from './Heading.js'
6 |
7 | export function H1(
8 | props: DetailedHTMLProps, HTMLHeadingElement>,
9 | ) {
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H2.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, primitiveColorVars, spaceVars } from '../../styles/vars.css.js'
3 | import { root as header } from './Header.css.js'
4 |
5 | export const root = style({
6 | fontSize: fontSizeVars.h2,
7 | letterSpacing: '-0.02em',
8 | selectors: {
9 | '&&:not(:last-child)': {
10 | marginBottom: spaceVars['24'],
11 | },
12 | [`:not(${header}) + &:not(:only-child)`]: {
13 | borderTop: `1px solid ${primitiveColorVars.border}`,
14 | marginTop: spaceVars['56'],
15 | paddingTop: spaceVars['24'],
16 | },
17 | '[data-layout="landing"] &&': {
18 | borderTop: 'none',
19 | marginTop: spaceVars['24'],
20 | paddingTop: 0,
21 | },
22 | },
23 | })
24 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H2.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './H2.css.js'
5 | import { Heading } from './Heading.js'
6 |
7 | export function H2(
8 | props: DetailedHTMLProps, HTMLHeadingElement>,
9 | ) {
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H3.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, spaceVars } from '../../styles/vars.css.js'
3 | import { root as H2 } from './H2.css.js'
4 |
5 | export const root = style({
6 | fontSize: fontSizeVars.h3,
7 | selectors: {
8 | '&:not(:first-child)': {
9 | marginTop: spaceVars['18'],
10 | paddingTop: spaceVars['18'],
11 | },
12 | '&&:not(:last-child)': {
13 | marginBottom: spaceVars['24'],
14 | },
15 | [`${H2}+&`]: {
16 | paddingTop: spaceVars['0'],
17 | },
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H3.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './H3.css.js'
5 | import { Heading } from './Heading.js'
6 |
7 | export function H3(
8 | props: DetailedHTMLProps, HTMLHeadingElement>,
9 | ) {
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H4.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, spaceVars } from '../../styles/vars.css.js'
3 | import { root as H3 } from './H3.css.js'
4 |
5 | export const root = style({
6 | fontSize: fontSizeVars.h4,
7 | selectors: {
8 | '&:not(:first-child)': {
9 | marginTop: spaceVars['18'],
10 | paddingTop: spaceVars['12'],
11 | },
12 | '&&:not(:last-child)': {
13 | marginBottom: spaceVars['24'],
14 | },
15 | [`${H3}+&`]: {
16 | paddingTop: spaceVars['0'],
17 | },
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H4.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './H4.css.js'
5 | import { Heading } from './Heading.js'
6 |
7 | export function H4(
8 | props: DetailedHTMLProps, HTMLHeadingElement>,
9 | ) {
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H5.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, spaceVars } from '../../styles/vars.css.js'
3 | import { root as H4 } from './H4.css.js'
4 |
5 | export const root = style({
6 | fontSize: fontSizeVars.h5,
7 | selectors: {
8 | '&:not(:first-child)': {
9 | marginTop: spaceVars['16'],
10 | },
11 | '&&:not(:last-child)': {
12 | marginBottom: spaceVars['24'],
13 | },
14 | [`${H4}+&`]: {
15 | paddingTop: spaceVars['0'],
16 | },
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H5.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './H5.css.js'
5 | import { Heading } from './Heading.js'
6 |
7 | export function H5(
8 | props: DetailedHTMLProps, HTMLHeadingElement>,
9 | ) {
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H6.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, spaceVars } from '../../styles/vars.css.js'
3 | import { root as H5 } from './H5.css.js'
4 |
5 | export const root = style({
6 | fontSize: fontSizeVars.h6,
7 | selectors: {
8 | '&:not(:first-child)': {
9 | marginTop: spaceVars['16'],
10 | },
11 | '&&:not(:last-child)': {
12 | marginBottom: spaceVars['24'],
13 | },
14 | [`${H5}+&`]: {
15 | paddingTop: spaceVars['0'],
16 | },
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/src/app/components/mdx/H6.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './H6.css.js'
5 | import { Heading } from './Heading.js'
6 |
7 | export function H6(
8 | props: DetailedHTMLProps, HTMLHeadingElement>,
9 | ) {
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Header.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { primitiveColorVars, spaceVars } from '../../styles/vars.css.js'
3 |
4 | export const root = style({
5 | borderBottom: `1px solid ${primitiveColorVars.border}`,
6 | selectors: {
7 | '&:not(:last-child)': {
8 | marginBottom: spaceVars['28'],
9 | paddingBottom: spaceVars['28'],
10 | },
11 | '[data-layout="landing"] &': {
12 | paddingBottom: spaceVars['16'],
13 | },
14 | '[data-layout="landing"] &:not(:first-child)': {
15 | paddingTop: spaceVars['36'],
16 | },
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Header.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Header.css.js'
5 |
6 | export function Header(props: DetailedHTMLProps, HTMLElement>) {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Heading.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import {
3 | fontWeightVars,
4 | lineHeightVars,
5 | primitiveColorVars,
6 | spaceVars,
7 | topNavVars,
8 | viewportVars,
9 | } from '../../styles/vars.css.js'
10 |
11 | import { title as Step_title } from '../Step.css.js'
12 | import { root as Header } from './Header.css.js'
13 |
14 | export const root = style({
15 | alignItems: 'center',
16 | color: primitiveColorVars.heading,
17 | fontWeight: fontWeightVars.semibold,
18 | gap: '0.25em',
19 | lineHeight: lineHeightVars.heading,
20 | position: 'relative',
21 | })
22 |
23 | export const slugTarget = style(
24 | {
25 | position: 'absolute',
26 | top: '0px',
27 | visibility: 'hidden',
28 | '@media': {
29 | [viewportVars['min-1080px']]: {
30 | top: `calc(-1 * (${topNavVars.height}))`,
31 | selectors: {
32 | [`${Header} &, ${Step_title} &, ${Header} + ${root} &`]: {
33 | top: `calc(-1 * (${topNavVars.height} + ${spaceVars['24']}))`,
34 | },
35 | },
36 | },
37 | [viewportVars['max-1080px']]: {
38 | top: `calc(-1 * ${topNavVars.curtainHeight})`,
39 | selectors: {
40 | [`${Header} &, ${Header} + ${root} &`]: {
41 | top: `calc(-1 * calc(${topNavVars.curtainHeight} + ${spaceVars['24']}))`,
42 | },
43 | },
44 | },
45 | },
46 | },
47 | 'slugTarget',
48 | )
49 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Heading.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import { root, slugTarget } from './Heading.css.js'
5 |
6 | export function Heading({
7 | level,
8 | ...props
9 | }: DetailedHTMLProps, HTMLHeadingElement> & {
10 | level: 1 | 2 | 3 | 4 | 5 | 6
11 | }) {
12 | const Component = `h${level}` as any
13 | return (
14 |
15 |
16 | {props.children}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/components/mdx/HorizontalRule.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { semanticColorVars, spaceVars } from '../../styles/vars.css.js'
3 |
4 | export const root = style({
5 | borderTop: `1px solid ${semanticColorVars.hr}`,
6 | marginBottom: spaceVars['16'],
7 | })
8 |
--------------------------------------------------------------------------------
/src/app/components/mdx/HorizontalRule.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './HorizontalRule.css.js'
5 |
6 | export function HorizontalRule(
7 | props: DetailedHTMLProps, HTMLHRElement>,
8 | ) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Kbd.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import {
4 | borderRadiusVars,
5 | fontFamilyVars,
6 | fontSizeVars,
7 | primitiveColorVars,
8 | spaceVars,
9 | } from '../../styles/vars.css.js'
10 |
11 | export const root = style({
12 | color: primitiveColorVars.text2,
13 | display: 'inline-block',
14 | borderRadius: borderRadiusVars['3'],
15 | fontSize: fontSizeVars['11'],
16 | fontFamily: fontFamilyVars.default,
17 | fontFeatureSettings: 'cv08',
18 | lineHeight: '105%',
19 | minWidth: '20px',
20 | padding: spaceVars['3'],
21 | paddingLeft: spaceVars['4'],
22 | paddingRight: spaceVars['4'],
23 | paddingTop: spaceVars['3'],
24 | textAlign: 'center',
25 | textTransform: 'capitalize',
26 | verticalAlign: 'baseline',
27 |
28 | border: `0.5px solid ${primitiveColorVars.border}`,
29 | backgroundColor: primitiveColorVars.background3,
30 | boxShadow: `${primitiveColorVars.shadow2} 0px 2px 0px 0px`,
31 | })
32 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Kbd.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Kbd.css.js'
5 |
6 | export function Kbd(props: DetailedHTMLProps, HTMLElement>) {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/List.css.ts:
--------------------------------------------------------------------------------
1 | import { globalStyle, style } from '@vanilla-extract/css'
2 |
3 | import { spaceVars } from '../../styles/vars.css.js'
4 | import { root as H2 } from './H2.css.js'
5 | import { root as H3 } from './H3.css.js'
6 | import { root as H4 } from './H4.css.js'
7 | import { root as H5 } from './H5.css.js'
8 | import { root as H6 } from './H6.css.js'
9 |
10 | export const root = style({
11 | selectors: {
12 | [`${H2}+&,${H3}+&,${H4}+&,${H5}+&,${H6}+&`]: {
13 | marginTop: `calc(${spaceVars['8']} * -1)`,
14 | },
15 | '.vocs_Paragraph + &': {
16 | marginTop: `calc(-1 * ${spaceVars['8']})`,
17 | },
18 | },
19 | })
20 |
21 | export const ordered = style(
22 | {
23 | listStyle: 'decimal',
24 | paddingLeft: spaceVars['20'],
25 | marginBottom: spaceVars['16'],
26 | selectors: {
27 | '& &': {
28 | listStyle: 'lower-alpha',
29 | },
30 | '& & &': {
31 | listStyle: 'lower-roman',
32 | },
33 | },
34 | },
35 | 'ordered',
36 | )
37 |
38 | export const unordered = style(
39 | {
40 | listStyle: 'disc',
41 | paddingLeft: spaceVars['24'],
42 | marginBottom: spaceVars['16'],
43 | selectors: {
44 | '& &': {
45 | listStyle: 'circle',
46 | },
47 | },
48 | },
49 | 'unordered',
50 | )
51 |
52 | globalStyle(
53 | [
54 | `${ordered} ${ordered}`,
55 | `${unordered} ${unordered}`,
56 | `${ordered} ${unordered}`,
57 | `${unordered} ${ordered}`,
58 | ].join(','),
59 | {
60 | marginBottom: spaceVars['0'],
61 | paddingTop: spaceVars['8'],
62 | paddingLeft: spaceVars['16'],
63 | paddingBottom: spaceVars['0'],
64 | },
65 | )
66 |
67 | globalStyle(`${unordered}.contains-task-list`, {
68 | listStyle: 'none',
69 | paddingLeft: spaceVars['12'],
70 | })
71 |
--------------------------------------------------------------------------------
/src/app/components/mdx/List.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './List.css.js'
5 |
6 | export function List({
7 | ordered,
8 | ...props
9 | }: DetailedHTMLProps, any> & { ordered?: boolean }) {
10 | const Element = ordered ? 'ol' : 'ul'
11 | return (
12 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/components/mdx/ListItem.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { lineHeightVars } from '../../styles/vars.css.js'
3 |
4 | export const root = style({
5 | lineHeight: lineHeightVars.listItem,
6 | selectors: {
7 | '&:not(:last-child)': {
8 | marginBottom: '0.5em',
9 | },
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/ListItem.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './ListItem.css.js'
5 |
6 | export function ListItem(props: DetailedHTMLProps, HTMLLIElement>) {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Paragraph.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { lineHeightVars, semanticColorVars, spaceVars } from '../../styles/vars.css.js'
4 | import { root as Blockquote } from './Blockquote.css.js'
5 | import { root as H2 } from './H2.css.js'
6 | import { root as H3 } from './H3.css.js'
7 | import { root as H4 } from './H4.css.js'
8 | import { root as H5 } from './H5.css.js'
9 | import { root as H6 } from './H6.css.js'
10 | import { root as List } from './List.css.js'
11 |
12 | export const root = style({
13 | lineHeight: lineHeightVars.paragraph,
14 | letterSpacing: '0.005em',
15 | get selectors() {
16 | return {
17 | [`${Blockquote}>&`]: {
18 | color: semanticColorVars.blockquoteText,
19 | marginBottom: spaceVars['8'],
20 | },
21 | [`${H2}+&,${H3}+&,${H4}+&,${H5}+&,${H6}+&,${List}+&`]: {
22 | marginTop: `calc(${spaceVars['8']} * -1)`,
23 | },
24 | [`${root} + ${root}`]: {
25 | marginTop: `calc(-1 * ${spaceVars['8']})`,
26 | },
27 | }
28 | },
29 | })
30 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Paragraph.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Paragraph.css.js'
5 |
6 | export function Paragraph(
7 | props: DetailedHTMLProps, HTMLParagraphElement>,
8 | ) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Pre.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
5 | export const wrapper = style(
6 | {
7 | position: 'relative',
8 | },
9 | 'wrapper',
10 | )
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Section.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { primitiveColorVars, spaceVars } from '../../styles/vars.css.js'
3 |
4 | export const root = style({
5 | borderTop: `1px solid ${primitiveColorVars.border}`,
6 | marginTop: spaceVars['56'],
7 | paddingTop: spaceVars['24'],
8 | })
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Section.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import { Footnotes } from './Footnotes.js'
5 | import * as styles from './Section.css.js'
6 |
7 | export function Section(props: DetailedHTMLProps, HTMLElement>) {
8 | if ('data-footnotes' in props)
9 | return
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Span.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | export const root = style({})
4 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Span.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Span.css.js'
5 | import { TwoslashPopover } from './TwoslashPopover.js'
6 |
7 | export function Span(props: DetailedHTMLProps, HTMLSpanElement>) {
8 | const className = clsx(props.className, styles.root)
9 |
10 | if (props.className?.includes('twoslash-hover'))
11 | return
12 | return
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Steps.tsx:
--------------------------------------------------------------------------------
1 | import { type ReactNode, cloneElement } from 'react'
2 |
3 | import * as stepStyles from '../Step.css.js'
4 | import { Step } from '../Step.js'
5 | import { Steps as Steps_ } from '../Steps.js'
6 |
7 | export function Steps({ children }: { children: ReactNode }) {
8 | if (!Array.isArray(children)) return null
9 | return (
10 |
11 | {children.map(({ props }, i) => {
12 | const [title, ...children] = Array.isArray(props.children)
13 | ? props.children
14 | : [props.children]
15 | return (
16 |
17 | {children}
18 |
19 | )
20 | })}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Strong.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { fontWeightVars, lineHeightVars, spaceVars } from '../../styles/vars.css.js'
4 | import { content as Callout } from '../Callout.css.js'
5 | import { root as Content } from '../Content.css.js'
6 |
7 | export const root = style({
8 | fontWeight: fontWeightVars.semibold,
9 | lineHeight: lineHeightVars.paragraph,
10 | selectors: {
11 | [`${Content} > &`]: {
12 | display: 'block',
13 | },
14 | [`${Callout} > &`]: {
15 | display: 'block',
16 | marginBottom: spaceVars['4'],
17 | },
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Strong.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Strong.css.js'
5 |
6 | export function Strong(props: DetailedHTMLProps, HTMLElement>) {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Subtitle.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import {
3 | fontSizeVars,
4 | fontWeightVars,
5 | lineHeightVars,
6 | primitiveColorVars,
7 | spaceVars,
8 | } from '../../styles/vars.css.js'
9 |
10 | export const root = style({
11 | color: primitiveColorVars.text2,
12 | fontSize: fontSizeVars.subtitle,
13 | fontWeight: fontWeightVars.regular,
14 | lineHeight: lineHeightVars.heading,
15 | marginTop: spaceVars['4'],
16 | textWrap: 'balance',
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Subtitle.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from 'react'
2 |
3 | import * as styles from './Subtitle.css.js'
4 |
5 | export function Subtitle({ children }: { children: ReactNode }) {
6 | return {children}
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Summary.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 |
3 | import { fontWeightVars, lineHeightVars, spaceVars } from '../../styles/vars.css.js'
4 | import { content as Callout } from '../Callout.css.js'
5 | import { root as Details } from './Details.css.js'
6 |
7 | export const root = style({
8 | cursor: 'pointer',
9 | lineHeight: lineHeightVars.paragraph,
10 | selectors: {
11 | '&&:hover': {
12 | textDecoration: 'underline',
13 | },
14 | [`${Details}[open] &`]: {
15 | marginBottom: spaceVars['4'],
16 | },
17 | [`${Callout} &`]: {
18 | fontWeight: fontWeightVars.medium,
19 | },
20 | [`${Details} &&`]: {
21 | marginBottom: 0,
22 | },
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Summary.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Summary.css.js'
5 |
6 | export function Summary(props: DetailedHTMLProps, HTMLElement>) {
7 | return
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Table.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { spaceVars } from '../../styles/vars.css.js'
3 |
4 | export const root = style({
5 | display: 'block',
6 | borderCollapse: 'collapse',
7 | overflowX: 'auto',
8 | marginBottom: spaceVars['24'],
9 | })
10 |
--------------------------------------------------------------------------------
/src/app/components/mdx/Table.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './Table.css.js'
5 |
6 | export function Table(
7 | props: DetailedHTMLProps, HTMLTableElement>,
8 | ) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/TableCell.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { fontSizeVars, semanticColorVars, spaceVars } from '../../styles/vars.css.js'
3 |
4 | export const root = style({
5 | border: `1px solid ${semanticColorVars.tableBorder}`,
6 | fontSize: fontSizeVars.td,
7 | padding: `${spaceVars['8']} ${spaceVars['12']}`,
8 | })
9 |
--------------------------------------------------------------------------------
/src/app/components/mdx/TableCell.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './TableCell.css.js'
5 |
6 | export function TableCell(
7 | props: DetailedHTMLProps, HTMLTableCellElement>,
8 | ) {
9 | return |
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/TableHeader.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import {
3 | fontSizeVars,
4 | fontWeightVars,
5 | semanticColorVars,
6 | spaceVars,
7 | } from '../../styles/vars.css.js'
8 |
9 | export const root = style({
10 | border: `1px solid ${semanticColorVars.tableBorder}`,
11 | backgroundColor: semanticColorVars.tableHeaderBackground,
12 | color: semanticColorVars.tableHeaderText,
13 | fontSize: fontSizeVars.th,
14 | fontWeight: fontWeightVars.medium,
15 | padding: `${spaceVars['8']} ${spaceVars['12']}`,
16 | textAlign: 'left',
17 | selectors: {
18 | '&[align="center"]': {
19 | textAlign: 'center',
20 | },
21 | '&[align="right"]': {
22 | textAlign: 'right',
23 | },
24 | },
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/components/mdx/TableHeader.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './TableHeader.css.js'
5 |
6 | export function TableHeader(
7 | props: DetailedHTMLProps, HTMLTableCellElement>,
8 | ) {
9 | return |
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/components/mdx/TableRow.css.ts:
--------------------------------------------------------------------------------
1 | import { style } from '@vanilla-extract/css'
2 | import { primitiveColorVars, semanticColorVars } from '../../styles/vars.css.js'
3 |
4 | export const root = style({
5 | borderTop: `1px solid ${semanticColorVars.tableBorder}`,
6 | selectors: {
7 | '&:nth-child(2n)': {
8 | backgroundColor: primitiveColorVars.background2,
9 | },
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/src/app/components/mdx/TableRow.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx'
2 | import type { DetailedHTMLProps, HTMLAttributes } from 'react'
3 |
4 | import * as styles from './TableRow.css.js'
5 |
6 | export function TableRow(
7 | props: DetailedHTMLProps, HTMLTableRowElement>,
8 | ) {
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/dom.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/src/app/hooks/useActiveNavIds.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import type { ParsedTopNavItem } from '../../config.js'
3 |
4 | function getActiveNavIds({
5 | items,
6 | pathname,
7 | }: { items: ParsedTopNavItem[]; pathname: string }): number[] {
8 | const path = pathname.replace(/\.html$/, '')
9 | const activeIds = []
10 | for (const item of items) {
11 | if (item.link && path.startsWith(item.match || item.link)) activeIds.push(item.id)
12 | else if (item.items) {
13 | const activeChildItems = getActiveNavIds({ items: item.items, pathname })
14 | if (activeChildItems.length > 0) activeIds.push(item.id)
15 | }
16 | }
17 | return activeIds
18 | }
19 |
20 | export function useActiveNavIds({
21 | items,
22 | pathname,
23 | }: { items: ParsedTopNavItem[]; pathname: string }): number[] {
24 | return useMemo(() => getActiveNavIds({ items, pathname }), [items, pathname])
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/hooks/useConfig.tsx:
--------------------------------------------------------------------------------
1 | import { config as virtualConfig } from 'virtual:config'
2 | import { sha256 } from '@noble/hashes/sha256'
3 | import { bytesToHex } from '@noble/hashes/utils'
4 | import { type ReactNode, createContext, useContext, useEffect, useState } from 'react'
5 | import { type ParsedConfig, deserializeConfig, serializeConfig } from '../../config.js'
6 |
7 | const ConfigContext = createContext(virtualConfig)
8 |
9 | export const configHash = import.meta.env.DEV
10 | ? bytesToHex(sha256(serializeConfig(virtualConfig))).slice(0, 8)
11 | : ''
12 |
13 | export function getConfig(): ParsedConfig {
14 | if (typeof window !== 'undefined' && import.meta.env.DEV) {
15 | const storedConfig = window.localStorage.getItem(`vocs.config.${configHash}`)
16 | if (storedConfig) return deserializeConfig(storedConfig)
17 | }
18 | return virtualConfig
19 | }
20 |
21 | export function ConfigProvider({
22 | children,
23 | config: initialConfig,
24 | }: { children: ReactNode; config?: ParsedConfig }) {
25 | const [config, setConfig] = useState(() => {
26 | if (initialConfig) return initialConfig
27 | return getConfig()
28 | })
29 |
30 | useEffect(() => {
31 | if (import.meta.hot) import.meta.hot.on('vocs:config', setConfig)
32 | }, [])
33 |
34 | useEffect(() => {
35 | if (typeof window !== 'undefined' && import.meta.env.DEV)
36 | window.localStorage.setItem(`vocs.config.${configHash}`, serializeConfig(config))
37 | }, [config])
38 |
39 | return {children}
40 | }
41 |
42 | export function useConfig() {
43 | return useContext(ConfigContext)
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/hooks/useCopyCode.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react'
2 |
3 | export function useCopyCode() {
4 | const ref = useRef(null)
5 |
6 | const [copied, setCopied] = useState(false)
7 |
8 | useEffect(() => {
9 | if (!copied) return
10 | const timeout = setTimeout(() => setCopied(false), 1000)
11 | return () => clearTimeout(timeout)
12 | }, [copied])
13 |
14 | function copy() {
15 | setCopied(true)
16 |
17 | const node = ref.current?.cloneNode(true) as HTMLPreElement
18 | const nodesToRemove = node?.querySelectorAll(
19 | 'button,.line.diff.remove,.twoslash-popup-info-hover,.twoslash-popup-info,.twoslash-meta-line,.twoslash-tag-line',
20 | )
21 | for (const node of nodesToRemove ?? []) node.remove()
22 | navigator.clipboard.writeText(node?.textContent as string)
23 | }
24 |
25 | return {
26 | copied,
27 | copy,
28 | ref,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/hooks/useDebounce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export function useDebounce(value: T, delay?: number): T {
4 | const [debouncedValue, setDebouncedValue] = useState(value)
5 |
6 | useEffect(() => {
7 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500)
8 |
9 | return () => {
10 | clearTimeout(timer)
11 | }
12 | }, [value, delay])
13 |
14 | return debouncedValue
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/hooks/useEditLink.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 |
3 | import { useConfig } from './useConfig.js'
4 | import { usePageData } from './usePageData.js'
5 |
6 | export function useEditLink() {
7 | const pageData = usePageData()
8 | const config = useConfig()
9 |
10 | return useMemo(() => {
11 | const { pattern = '', text = 'Edit page' } = config.editLink ?? {}
12 |
13 | let url = ''
14 | // TODO: pattern as function
15 | if (typeof pattern === 'function') url = ''
16 | else if (pageData.filePath) url = pattern.replace(/:path/g, pageData.filePath)
17 |
18 | return { url, text }
19 | }, [config.editLink, pageData.filePath])
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/hooks/useLayout.tsx:
--------------------------------------------------------------------------------
1 | import type { Layout } from '../types.js'
2 | import { useConfig } from './useConfig.js'
3 | import { usePageData } from './usePageData.js'
4 | import { useSidebar } from './useSidebar.js'
5 |
6 | export function useLayout(): Layout {
7 | const { aiCta } = useConfig()
8 | const sidebar = useSidebar()
9 | const { frontmatter } = usePageData()
10 | const {
11 | layout: layout_,
12 | showLogo,
13 | showAiCta,
14 | showOutline,
15 | showSidebar,
16 | showTopNav,
17 | } = frontmatter || {}
18 |
19 | const layout = layout_ ?? 'docs'
20 |
21 | return {
22 | layout,
23 | get showLogo() {
24 | if (typeof showLogo !== 'undefined') return showLogo
25 | return true
26 | },
27 | get showAiCta() {
28 | if (typeof showAiCta !== 'undefined') return showAiCta
29 | if (aiCta === false) return false
30 | return layout === 'docs'
31 | },
32 | get showOutline() {
33 | if (typeof showOutline !== 'undefined') return showOutline
34 | return layout === 'docs'
35 | },
36 | get showSidebar() {
37 | if (sidebar.items.length === 0) return false
38 | if (typeof showSidebar !== 'undefined') return showSidebar
39 | if (layout === 'minimal') return false
40 | if (layout === 'landing') return false
41 | return true
42 | },
43 | get showTopNav() {
44 | if (typeof showTopNav !== 'undefined') return showTopNav
45 | return true
46 | },
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/hooks/useLocalStorage.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react'
2 |
3 | type SetValue = (newVal: type | ((prevVal: type) => type)) => void
4 |
5 | export function useLocalStorage(
6 | key: string,
7 | defaultValue: type | undefined,
8 | ): [type | undefined, SetValue] {
9 | const [value, setValue] = useState()
10 |
11 | useEffect(() => {
12 | const initialValue = getItem(key) as type | undefined
13 |
14 | if (typeof initialValue === 'undefined' || initialValue === null) {
15 | setValue(typeof defaultValue === 'function' ? defaultValue() : defaultValue)
16 | } else {
17 | setValue(initialValue)
18 | }
19 | }, [defaultValue, key])
20 |
21 | const setter = useCallback(
22 | (updater: type | ((prevVal: type) => type)) => {
23 | setValue((old) => {
24 | let newVal: type
25 | if (typeof updater === 'function') newVal = (updater as any)(old)
26 | else newVal = updater
27 |
28 | try {
29 | localStorage.setItem(key, JSON.stringify(newVal))
30 | } catch {}
31 |
32 | return newVal
33 | })
34 | },
35 | [key],
36 | )
37 |
38 | return [value, setter]
39 | }
40 |
41 | function getItem(key: string): unknown {
42 | try {
43 | const itemValue = localStorage.getItem(key)
44 | if (typeof itemValue === 'string') {
45 | return JSON.parse(itemValue)
46 | }
47 | return undefined
48 | } catch {
49 | return undefined
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/hooks/useMounted.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export function useMounted() {
4 | const [mounted, setMounted] = useState(false)
5 | useEffect(() => {
6 | setMounted(true)
7 | }, [])
8 | return mounted
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/hooks/useOgImageUrl.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useLocation } from 'react-router'
3 |
4 | import { useConfig } from './useConfig.js'
5 |
6 | export function useOgImageUrl(): string | undefined {
7 | const { pathname } = useLocation()
8 | const config = useConfig()
9 | const { ogImageUrl } = config
10 |
11 | if (!ogImageUrl) return undefined
12 | if (typeof ogImageUrl === 'string') return ogImageUrl
13 |
14 | const pathKey = useMemo(() => {
15 | const keys = Object.keys(ogImageUrl).filter((key) => pathname.startsWith(key))
16 | return keys[keys.length - 1]
17 | }, [ogImageUrl, pathname])
18 | if (!pathKey) return undefined
19 |
20 | return ogImageUrl[pathKey]
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/hooks/usePageData.ts:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from 'react'
2 |
3 | import type { Module } from '../types.js'
4 |
5 | export function usePageData() {
6 | const pageData = useContext(PageDataContext)
7 | if (!pageData) throw new Error('`usePageData` must be used within `PageDataContext.Provider`.')
8 | return pageData
9 | }
10 |
11 | export const PageDataContext = createContext<
12 | | {
13 | content?: string
14 | filePath?: string
15 | frontmatter: Module['frontmatter']
16 | lastUpdatedAt?: number
17 | previousPath?: string
18 | }
19 | | undefined
20 | >(undefined)
21 |
--------------------------------------------------------------------------------
/src/app/hooks/useSearchIndex.ts:
--------------------------------------------------------------------------------
1 | import { getSearchIndex } from 'virtual:searchIndex'
2 | import MiniSearch from 'minisearch'
3 | import { useEffect, useState } from 'react'
4 |
5 | export type Result = {
6 | href: string
7 | html: string
8 | isPage: boolean
9 | text?: string
10 | title: string
11 | titles: string[]
12 | }
13 |
14 | let promise: Promise
15 |
16 | export function useSearchIndex(): MiniSearch | undefined {
17 | const [searchIndex, setSearchIndex] = useState>()
18 |
19 | useEffect(() => {
20 | ;(async () => {
21 | if (!promise) promise = getSearchIndex()
22 | const json = await promise
23 | const searchIndex = MiniSearch.loadJSON(json, {
24 | fields: ['title', 'titles', 'text'],
25 | searchOptions: {
26 | boost: { title: 4, text: 2, titles: 1 },
27 | fuzzy: 0.2,
28 | prefix: true,
29 | // ...(theme.value.search?.provider === 'local' &&
30 | // theme.value.search.options?.miniSearch?.searchOptions),
31 | },
32 | storeFields: ['href', 'html', 'isPage', 'text', 'title', 'titles'],
33 | // ...(theme.value.search?.provider === 'local' &&
34 | // theme.value.search.options?.miniSearch?.options),
35 | })
36 | setSearchIndex(searchIndex)
37 | })()
38 | }, [])
39 |
40 | useEffect(() => {
41 | if (!import.meta.hot) return
42 |
43 | // TODO: Update index
44 | import.meta.hot.accept('virtual:searchIndex', (m) => {
45 | if (m) {
46 | console.log('update', m)
47 | }
48 | })
49 | }, [])
50 |
51 | return searchIndex
52 | }
53 |
--------------------------------------------------------------------------------
/src/app/hooks/useSidebar.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useLocation } from 'react-router'
3 |
4 | import type { SidebarItem } from '../../config.js'
5 | import { useConfig } from './useConfig.js'
6 |
7 | type UseSidebarReturnType = { backLink?: boolean; items: SidebarItem[]; key?: string }
8 |
9 | export function useSidebar(): UseSidebarReturnType {
10 | const { pathname } = useLocation()
11 | const config = useConfig()
12 | const { sidebar } = config
13 |
14 | if (!sidebar) return { items: [] }
15 | if (Array.isArray(sidebar)) return { items: sidebar }
16 |
17 | const sidebarKey = useMemo(() => {
18 | const keys = Object.keys(sidebar).filter((key) => pathname.startsWith(key))
19 | return keys[keys.length - 1]
20 | }, [sidebar, pathname])
21 | if (!sidebarKey) return { items: [] }
22 |
23 | if (Array.isArray(sidebar[sidebarKey]))
24 | return { key: sidebarKey, items: sidebar[sidebarKey] } as UseSidebarReturnType
25 | return { ...sidebar[sidebarKey], key: sidebarKey } as UseSidebarReturnType
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/hooks/useTheme.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | export function useTheme() {
4 | const [theme, setTheme] = useState(() => {
5 | if (typeof window === 'undefined') return undefined
6 | if (localStorage.getItem('vocs.theme')) {
7 | const storedTheme = localStorage.getItem('vocs.theme')
8 | if (storedTheme) return storedTheme
9 | }
10 | return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
11 | })
12 |
13 | useEffect(() => {
14 | if (theme) localStorage.setItem('vocs.theme', theme)
15 |
16 | if (theme === 'dark') document.documentElement.classList.add('dark')
17 | else document.documentElement.classList.remove('dark')
18 | }, [theme])
19 |
20 | return {
21 | setTheme,
22 | theme,
23 | toggle() {
24 | setTheme((theme) => (theme === 'light' ? 'dark' : 'light'))
25 | },
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/index.client.tsx:
--------------------------------------------------------------------------------
1 | import './styles/index.css.js'
2 |
3 | import { hydrateRoot } from 'react-dom/client'
4 | import { RouterProvider, createBrowserRouter } from 'react-router'
5 | import { ConfigProvider, getConfig } from './hooks/useConfig.js'
6 | import { routes } from './routes.js'
7 | import { hydrateLazyRoutes } from './utils/hydrateLazyRoutes.js'
8 | import { removeTempStyles } from './utils/removeTempStyles.js'
9 |
10 | hydrate()
11 |
12 | async function hydrate() {
13 | const basePath = getConfig().basePath
14 |
15 | await hydrateLazyRoutes(routes, basePath)
16 | removeTempStyles()
17 |
18 | const router = createBrowserRouter(routes, { basename: basePath })
19 | hydrateRoot(
20 | document.getElementById('app')!,
21 |
22 |
23 | ,
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/public/.vocs/icons/arrow-diagonal.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/public/.vocs/icons/chevron-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/public/.vocs/icons/chevron-up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/public/.vocs/icons/link.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/app/styles/base.css.ts:
--------------------------------------------------------------------------------
1 | import { layer } from '@vanilla-extract/css'
2 |
3 | // Preflight layer designed to be used by consumers so
4 | // consumer styles don't override internal Vocs styles.
5 | // Example case: @tailwind CSS directives.
6 | layer()
7 |
--------------------------------------------------------------------------------
/src/app/styles/index.css.ts:
--------------------------------------------------------------------------------
1 | import './base.css.js'
2 |
3 | import './vars.css.js'
4 |
5 | import './reset.css.js'
6 |
7 | import './global.css.js'
8 |
9 | import './twoslash.css.js'
10 |
--------------------------------------------------------------------------------
/src/app/styles/utils.css.ts:
--------------------------------------------------------------------------------
1 | import { globalStyle, style } from '@vanilla-extract/css'
2 |
3 | export const visibleDark = style({}, 'visibleDark')
4 | globalStyle(`:root:not(.dark) ${visibleDark}`, {
5 | display: 'none',
6 | })
7 |
8 | export const visibleLight = style({}, 'visibleLight')
9 | globalStyle(`:root.dark ${visibleLight}`, {
10 | display: 'none',
11 | })
12 |
13 | export const visuallyHidden = style(
14 | {
15 | clip: 'rect(0 0 0 0)',
16 | clipPath: 'inset(50%)',
17 | height: 1,
18 | overflow: 'hidden',
19 | position: 'absolute',
20 | whiteSpace: 'nowrap',
21 | width: 1,
22 | },
23 | 'visuallyHidden',
24 | )
25 |
--------------------------------------------------------------------------------
/src/app/types.ts:
--------------------------------------------------------------------------------
1 | import type * as React from 'react'
2 |
3 | export type BlogPost = {
4 | authors?: string | string[]
5 | date?: string
6 | path: string
7 | title: string
8 | description: string
9 | }
10 |
11 | export type Frontmatter = {
12 | [key: string]: unknown
13 | authors?: string | string[]
14 | content?: {
15 | horizontalPadding?: string
16 | width?: string
17 | verticalPadding?: string
18 | }
19 | date?: string
20 | description?: string
21 | title?: string
22 | } & Partial
23 |
24 | export type Layout = {
25 | layout: 'docs' | 'landing' | 'minimal'
26 | showAiCta: boolean
27 | showLogo: boolean
28 | showOutline: number | boolean
29 | showSidebar: boolean
30 | showTopNav: boolean
31 | }
32 |
33 | export type Module = {
34 | default: React.ComponentType
35 | frontmatter?: Frontmatter
36 | }
37 |
38 | export type PageData = {
39 | filePath: string
40 | frontmatter?: Frontmatter
41 | }
42 |
43 | export type Route = {
44 | content?: string
45 | filePath: string
46 | lazy: () => Promise
47 | lastUpdatedAt?: number
48 | path: string
49 | type: 'jsx' | 'mdx'
50 | }
51 |
--------------------------------------------------------------------------------
/src/app/utils/createFetchRequest.ts:
--------------------------------------------------------------------------------
1 | export function createFetchRequest(req: any) {
2 | const origin = `${req.protocol}://${req.headers.host}`
3 | const url = new URL(req.originalUrl || req.url, origin)
4 |
5 | const controller = new AbortController()
6 | req.on('close', () => controller.abort())
7 |
8 | const headers = new Headers()
9 |
10 | for (const [key, values] of Object.entries(req.headers)) {
11 | if (values) {
12 | if (Array.isArray(values)) for (const value of values) headers.append(key, value)
13 | else headers.set(key, values as any)
14 | }
15 | }
16 |
17 | const init: RequestInit = {
18 | method: req.method,
19 | headers,
20 | signal: controller.signal,
21 | }
22 |
23 | if (req.method !== 'GET' && req.method !== 'HEAD') init.body = req.body
24 |
25 | return new Request(url.href, init)
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/utils/debounce.ts:
--------------------------------------------------------------------------------
1 | export function debounce(fn: () => void, delay: number): () => void {
2 | let invoked = false
3 | return () => {
4 | invoked = true
5 | setTimeout(() => {
6 | if (invoked) fn()
7 | invoked = false
8 | }, delay)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/utils/deserializeElement.ts:
--------------------------------------------------------------------------------
1 | import React, { type ReactElement, type ReactNode } from 'react'
2 |
3 | export function deserializeElement(element: ReactElement, key?: number): ReactNode {
4 | if (typeof element !== 'object') return element
5 | if (element === null) return element
6 | if (Array.isArray(element)) return element.map((el, i) => deserializeElement(el, i))
7 |
8 | const props: any = element.props.children
9 | ? { ...element.props, children: deserializeElement(element.props.children) }
10 | : element.props
11 | return React.createElement(element.type, { ...props, key })
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/utils/hydrateLazyRoutes.ts:
--------------------------------------------------------------------------------
1 | import { type RouteObject, matchRoutes } from 'react-router'
2 |
3 | export async function hydrateLazyRoutes(routes: RouteObject[], basePath: string | undefined) {
4 | // Determine if any of the initial routes are lazy
5 | const lazyMatches = matchRoutes(routes, window.location, basePath)?.filter((m) => m.route.lazy)
6 |
7 | // Load the lazy matches and update the routes before creating your router
8 | // so we can hydrate the SSR-rendered content synchronously
9 | if (lazyMatches && lazyMatches?.length > 0) {
10 | await Promise.all(
11 | lazyMatches.map(async (m) => {
12 | const routeModule = await m.route.lazy!()
13 | Object.assign(m.route, {
14 | ...routeModule,
15 | lazy: undefined,
16 | })
17 | }),
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/utils/initializeTheme.ts:
--------------------------------------------------------------------------------
1 | const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
2 |
3 | const hasTheme =
4 | document.documentElement.classList.contains('dark') ||
5 | document.documentElement.classList.contains('light')
6 |
7 | if (!hasTheme) {
8 | const storedTheme = localStorage.getItem('vocs.theme')
9 | const theme = storedTheme || (darkModeMediaQuery.matches ? 'dark' : 'light')
10 |
11 | if (theme === 'dark') document.documentElement.classList.add('dark')
12 |
13 | if (!storedTheme)
14 | // Update the theme if the user changes their OS preference
15 | darkModeMediaQuery.addEventListener('change', ({ matches: isDark }) => {
16 | if (isDark) document.documentElement.classList.add('dark')
17 | else document.documentElement.classList.remove('dark')
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/utils/mergeRefs.ts:
--------------------------------------------------------------------------------
1 | import type { MutableRefObject, RefCallback } from 'react'
2 |
3 | type MutableRefList = Array | MutableRefObject | undefined | null>
4 |
5 | export function mergeRefs(...refs: MutableRefList): RefCallback {
6 | return (val: T) => {
7 | setRef(val, ...refs)
8 | }
9 | }
10 |
11 | export function setRef(val: T, ...refs: MutableRefList): void {
12 | // biome-ignore lint/complexity/noForEach:
13 | refs.forEach((ref) => {
14 | if (typeof ref === 'function') {
15 | ref(val)
16 | } else if (ref != null) {
17 | ref.current = val
18 | }
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/utils/removeTempStyles.ts:
--------------------------------------------------------------------------------
1 | export function removeTempStyles() {
2 | const tempStyles = document.querySelectorAll('style[data-vocs-temp-style="true"]')
3 | for (const style of tempStyles) style.remove()
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module 'virtual:blog' {
4 | export const posts: import('./types.js').BlogPost[]
5 | }
6 |
7 | declare module 'virtual:config' {
8 | export const config: import('../config.js').ParsedConfig
9 | }
10 |
11 | declare module 'virtual:routes' {
12 | export const routes: import('./types.js').Route[]
13 | }
14 |
15 | declare module 'virtual:consumer-components' {
16 | export const Layout: import('react').ElementType
17 | export const Footer: import('react').ElementType
18 | export const TopNavEnd: import('react').ElementType
19 | }
20 |
21 | declare module 'virtual:searchIndex' {
22 | export const getSearchIndex: () => Promise
23 | }
24 |
--------------------------------------------------------------------------------
/src/cli/commands/dev.ts:
--------------------------------------------------------------------------------
1 | import pc from 'picocolors'
2 | import { createLogger } from 'vite'
3 | import { version } from '../version.js'
4 |
5 | type DevParameters = { clean?: boolean; clearScreen?: boolean; host?: boolean; port?: number }
6 |
7 | export async function dev(_: any, { clean, clearScreen = true, host, port }: DevParameters = {}) {
8 | const { createDevServer } = await import('../../vite/devServer.js')
9 |
10 | const server = await createDevServer({ clean, host, port })
11 | await server.listen()
12 |
13 | const logger = createLogger()
14 | if (clearScreen) logger.clearScreen('info')
15 | logger.info('')
16 | logger.info(` ${pc.green('[running]')} ${pc.bold('vocs')}@${pc.dim(`v${version}`)}`)
17 | logger.info('')
18 | server.printUrls()
19 | }
20 |
--------------------------------------------------------------------------------
/src/cli/commands/preview.ts:
--------------------------------------------------------------------------------
1 | import pc from 'picocolors'
2 | import { createLogger } from 'vite'
3 |
4 | import { resolveVocsConfig } from '../../vite/utils/resolveVocsConfig.js'
5 | import { version } from '../version.js'
6 |
7 | export async function preview() {
8 | const { preview } = await import('../../vite/preview.js')
9 | const server = await preview()
10 |
11 | const { config } = await resolveVocsConfig()
12 | const { basePath } = config
13 |
14 | const logger = createLogger()
15 | logger.clearScreen('info')
16 | logger.info('')
17 | logger.info(` ${pc.green('[running]')} ${pc.bold('vocs')}@${pc.dim(`v${version}`)}`)
18 | logger.info('')
19 |
20 | logger.info(
21 | ` ${pc.green('➜')} ${pc.bold('Local')}: ${pc.cyan(
22 | `http://localhost:${server.port}${basePath}`,
23 | )}`,
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/cli/commands/search-index.ts:
--------------------------------------------------------------------------------
1 | import pc from 'picocolors'
2 | import { createLogger } from 'vite'
3 | import type { BuildSearchIndexParameters } from '../../vite/buildSearchIndex.js'
4 | import { buildSearchIndex } from '../../vite/buildSearchIndex.js'
5 | import { version } from '../version.js'
6 |
7 | export type SearchIndexParameters = BuildSearchIndexParameters
8 |
9 | export async function searchIndex({ outDir }: SearchIndexParameters) {
10 | const start = Date.now()
11 |
12 | const logger = createLogger('info', { allowClearScreen: true })
13 |
14 | logger.clearScreen('info')
15 | logger.info('')
16 | logger.info(` ${pc.blue('[indexing]')} ${pc.bold('vocs')}@${pc.dim(`v${version}`)}\n`)
17 |
18 | await buildSearchIndex({ outDir })
19 |
20 | const end = Date.now()
21 | const time = end - start
22 | logger.info(`\n ${pc.green('[indexed]')} in ${time / 1000}s`)
23 | }
24 |
--------------------------------------------------------------------------------
/src/cli/commands/twoslash.ts:
--------------------------------------------------------------------------------
1 | import pc from 'picocolors'
2 | import { createLogger } from 'vite'
3 | import { buildTwoslash } from '../../vite/buildTwoslash.js'
4 | import { version } from '../version.js'
5 |
6 | export async function twoslash() {
7 | const start = Date.now()
8 |
9 | const logger = createLogger('info', { allowClearScreen: true })
10 |
11 | logger.clearScreen('info')
12 | logger.info('')
13 | logger.info(` ${pc.blue('[building twoslash]')} ${pc.bold('vocs')}@${pc.dim(`v${version}`)}\n`)
14 |
15 | await buildTwoslash()
16 |
17 | const end = Date.now()
18 | const time = end - start
19 | logger.info(`\n ${pc.green('[built twoslash]')} in ${time / 1000}s`)
20 | }
21 |
--------------------------------------------------------------------------------
/src/cli/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { cac } from 'cac'
3 |
4 | import { build } from './commands/build.js'
5 | import { dev } from './commands/dev.js'
6 | import { preview } from './commands/preview.js'
7 | import { searchIndex } from './commands/search-index.js'
8 | import { twoslash } from './commands/twoslash.js'
9 | import { version } from './version.js'
10 |
11 | export const cli = cac('vocs')
12 |
13 | cli
14 | .command('[root]')
15 | .alias('dev')
16 | .option('-c, --clean', 'clean the cache and re-bundle')
17 | .option('-h, --host', 'Expose host URL')
18 | .option('-p, --port [number]', 'Port used by the server (default: 5173)')
19 | .option('--clearScreen', 'clear the terminal screen (default: true)')
20 | .action(dev)
21 | cli
22 | .command('build')
23 | .option('-c, --clean', 'clean the cache and re-bundle')
24 | .option('-l, --logLevel [level]', 'info | warn | error | silent')
25 | .option('-o, --outDir [dir]', 'output directory (default: dist)')
26 | .option('-p, --publicDir [dir]', 'public (asset) directory (default: public)')
27 | .option('--clearScreen', 'clear the terminal screen (default: true)')
28 | .option('--searchIndex', 'builds the search index (default: true)')
29 | .action(build)
30 | cli.command('preview').action(preview)
31 | cli
32 | .command('search-index')
33 | .option('-o, --outDir [dir]', 'output directory (default: dist)')
34 | .action(searchIndex)
35 | cli.command('twoslash').action(twoslash)
36 |
37 | cli.help()
38 | cli.version(version)
39 |
40 | cli.parse()
41 |
--------------------------------------------------------------------------------
/src/cli/version.ts:
--------------------------------------------------------------------------------
1 | export const version = '1.0.0'
2 |
--------------------------------------------------------------------------------
/src/components.ts:
--------------------------------------------------------------------------------
1 | export { Authors, type AuthorsProps } from './app/components/Authors.js'
2 | export { BlogPosts } from './app/components/BlogPosts.js'
3 | export { Button } from './app/components/Button.js'
4 | export { Callout, type CalloutProps } from './app/components/Callout.js'
5 | export * as HomePage from './app/components/HomePage.js'
6 | export { Raw } from './app/components/Raw.js'
7 | export { Sponsors } from './app/components/Sponsors.js'
8 | export { Steps, type StepsProps } from './app/components/Steps.js'
9 | export { Step, type StepProps } from './app/components/Step.js'
10 |
11 | export { components as MDXComponents } from './app/components/mdx/index.js'
12 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { defineConfig } from './config.js'
2 | export type {
3 | ColorScheme,
4 | Config,
5 | EditLink,
6 | Font,
7 | IconUrl,
8 | LogoUrl,
9 | Sidebar,
10 | SidebarItem,
11 | SocialItem,
12 | SocialType,
13 | Socials,
14 | Theme,
15 | TopNav,
16 | TopNavItem,
17 | } from './config.js'
18 |
19 | export { build } from './vite/build.js'
20 | export { createDevServer } from './vite/devServer.js'
21 | export { preview } from './vite/preview.js'
22 |
--------------------------------------------------------------------------------
/src/mdx-react.ts:
--------------------------------------------------------------------------------
1 | export * from '@mdx-js/react'
2 |
--------------------------------------------------------------------------------
/src/vite/buildSearchIndex.ts:
--------------------------------------------------------------------------------
1 | import { resolveOutDir } from './utils/resolveOutDir.js'
2 | import { resolveVocsConfig } from './utils/resolveVocsConfig.js'
3 | import { buildIndex, saveIndex } from './utils/search.js'
4 |
5 | export type BuildSearchIndexParameters = {
6 | outDir?: string
7 | }
8 |
9 | export async function buildSearchIndex({ outDir }: BuildSearchIndexParameters) {
10 | const { config } = await resolveVocsConfig()
11 | const { cacheDir, rootDir } = config
12 | const outDir_resolved = resolveOutDir(rootDir, outDir)
13 |
14 | const index = await buildIndex({ baseDir: rootDir, cacheDir })
15 | saveIndex(outDir_resolved, index, { cacheDir })
16 | }
17 |
--------------------------------------------------------------------------------
/src/vite/buildTwoslash.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'node:fs'
2 | import { resolve } from 'node:path'
3 | import { compile } from '@mdx-js/mdx'
4 | import { globby } from 'globby'
5 | import { getRehypePlugins, getRemarkPlugins } from './plugins/mdx.js'
6 | import { resolveVocsConfig } from './utils/resolveVocsConfig.js'
7 |
8 | export async function buildTwoslash() {
9 | const { config } = await resolveVocsConfig()
10 | const { cacheDir, rootDir } = config
11 | const pagesPaths = await globby(`${resolve(rootDir, 'pages')}/**/*.{md,mdx}`)
12 |
13 | const rehypePlugins = getRehypePlugins({ cacheDir, rootDir })
14 | const remarkPlugins = getRemarkPlugins()
15 |
16 | for (const pagePath of pagesPaths) {
17 | const mdx = readFileSync(pagePath, 'utf-8')
18 | await compile(mdx, {
19 | outputFormat: 'function-body',
20 | rehypePlugins,
21 | remarkPlugins,
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/vite/devServer.ts:
--------------------------------------------------------------------------------
1 | import { createServer } from 'vite'
2 |
3 | import { dev } from './plugins/dev.js'
4 | import * as cache from './utils/cache.js'
5 | import { resolveVocsConfig } from './utils/resolveVocsConfig.js'
6 |
7 | export type CreateDevServerParameters = {
8 | clean?: boolean
9 | host?: boolean
10 | port?: number
11 | }
12 |
13 | export async function createDevServer(params: CreateDevServerParameters = {}) {
14 | const { config } = await resolveVocsConfig()
15 | const { cacheDir } = config
16 | if (params.clean) cache.clear({ cacheDir })
17 | return createServer({
18 | root: import.meta.dirname,
19 | server: {
20 | host: params.host,
21 | port: params.port,
22 | },
23 | plugins: [dev()],
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/src/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/vite/plugins/css.ts:
--------------------------------------------------------------------------------
1 | import { default as autoprefixer } from 'autoprefixer'
2 | import type { PluginOption } from 'vite'
3 |
4 | export function css(): PluginOption {
5 | return {
6 | name: 'css',
7 | async config() {
8 | return {
9 | css: {
10 | postcss: {
11 | plugins: [autoprefixer()].filter(Boolean) as any,
12 | },
13 | },
14 | }
15 | },
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/vite/plugins/postbuild.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { default as fs } from 'fs-extra'
3 | import pc from 'picocolors'
4 | import type { Logger, PluginOption } from 'vite'
5 |
6 | const deadlinksPath = resolve(import.meta.dirname, '../.vocs/cache/deadlinks.json')
7 |
8 | export function postbuild({ logger }: { logger?: Logger } = {}): PluginOption {
9 | return {
10 | name: 'postbuild',
11 | closeBundle() {
12 | if (!fs.existsSync(deadlinksPath)) return
13 |
14 | const deadlinks = fs.readJSONSync(deadlinksPath)
15 | logger?.error(
16 | [
17 | 'found dead links:',
18 | '',
19 | ...deadlinks.map(
20 | ([link, path]: [string, string]) => `${pc.red(link)} in ${pc.blue(path)}`,
21 | ),
22 | pc.italic(pc.gray('skip by setting link to "#TODO".')),
23 | '\n',
24 | ].join('\n'),
25 | {
26 | clear: true,
27 | timestamp: true,
28 | },
29 | )
30 | throw new Error('deadlinks found.')
31 | },
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/vite/plugins/rehype/display-shiki-notation.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function rehypeShikiDisplayNotation() {
8 | return (tree: Root) => {
9 | visit(tree, 'text', (node) => {
10 | if (node.value.includes('// [\\!'))
11 | node.value = node.value.replace('// [\\!', '// [!').replaceAll('// //', '')
12 | })
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/authors.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkAuthors() {
8 | return (tree: Root) => {
9 | visit(tree, (node, index, parent) => {
10 | if (node.type !== 'leafDirective') return
11 | if (node.name !== 'authors') return
12 | if (!index) return
13 | ;(parent?.children[index - 1] as any).children.push({
14 | type: 'paragraph',
15 | data: {
16 | hName: 'div',
17 | hProperties: { 'data-authors': true },
18 | },
19 | })
20 | })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/blog-posts.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkBlogPosts() {
8 | return (tree: Root) => {
9 | visit(tree, (node) => {
10 | if (node.type !== 'leafDirective') return
11 | if (node.name !== 'blog-posts') return
12 |
13 | const data = node.data || (node.data = {})
14 | data.hName = 'div'
15 | data.hProperties = { 'data-blog-posts': true }
16 | })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/callout.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import { h } from 'hastscript'
5 | import type { Root } from 'mdast'
6 | import { visit } from 'unist-util-visit'
7 |
8 | export function remarkCallout() {
9 | return (tree: Root) => {
10 | visit(tree, (node) => {
11 | if (node.type !== 'containerDirective') return
12 | if (
13 | node.name !== 'callout' &&
14 | node.name !== 'info' &&
15 | node.name !== 'warning' &&
16 | node.name !== 'danger' &&
17 | node.name !== 'tip' &&
18 | node.name !== 'success' &&
19 | node.name !== 'note'
20 | )
21 | return
22 |
23 | // @ts-expect-error
24 | const label = node.children.find((child) => child.data?.directiveLabel)?.children[0].value
25 |
26 | const data = node.data || (node.data = {})
27 | const tagName = 'aside'
28 |
29 | if (label) {
30 | node.children = node.children.filter((child: any) => !child.data?.directiveLabel)
31 | node.children.unshift({
32 | type: 'paragraph',
33 | data: { hProperties: { 'data-callout-title': true } },
34 | children: [
35 | {
36 | type: 'strong',
37 | children: [{ type: 'text', value: label }],
38 | },
39 | ],
40 | })
41 | }
42 |
43 | data.hName = tagName
44 | data.hProperties = {
45 | ...h(tagName, node.attributes || {}).properties,
46 | 'data-callout': node.name !== 'callout' ? node.name : true,
47 | }
48 | })
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/code-group.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import { h } from 'hastscript'
5 | import type { BlockContent, DefinitionContent, Root } from 'mdast'
6 | import { visit } from 'unist-util-visit'
7 |
8 | export function remarkCodeGroup() {
9 | return (tree: Root) => {
10 | visit(tree, (node) => {
11 | if (node.type !== 'containerDirective') return
12 | if (node.name !== 'code-group') return
13 |
14 | const data = node.data || (node.data = {})
15 | const tagName = 'div'
16 |
17 | node.attributes = {
18 | ...node.attributes,
19 | class: 'code-group',
20 | }
21 |
22 | data.hName = tagName
23 | data.hProperties = h(tagName, node.attributes || {}).properties
24 |
25 | node.children = node.children
26 | .map((child) => {
27 | const match = 'meta' in child && child?.meta?.match(/\[(.*)\]/)
28 | return {
29 | type: 'paragraph',
30 | children: [child],
31 | data: {
32 | hName: 'div',
33 | hProperties: match
34 | ? {
35 | 'data-title': match[1],
36 | }
37 | : undefined,
38 | },
39 | }
40 | })
41 | .filter(Boolean) as (BlockContent | DefinitionContent)[]
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/code.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkCode() {
8 | return (tree: Root) => {
9 | visit(tree, (node, _, parent) => {
10 | if (node.type !== 'code') return
11 | if (!node.lang) node.lang = 'markdown'
12 | if (parent?.type === 'containerDirective' && parent.name !== 'steps') return
13 |
14 | const [match, title] = node.meta?.match(/\[(.*)\]/) || []
15 | if (match) node.meta = node.meta?.replace(match, `title=\"${title}\"`)
16 | })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/details.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkDetails() {
8 | return (tree: Root) => {
9 | visit(tree, (node) => {
10 | if (node.type !== 'containerDirective') return
11 | if (node.name !== 'details') return
12 |
13 | const data = node.data || (node.data = {})
14 | const tagName = 'details'
15 |
16 | const summaryChild = node.children[0]
17 | if (summaryChild.type === 'paragraph' && summaryChild.data?.directiveLabel)
18 | summaryChild.data.hName = 'summary'
19 | else
20 | node.children.unshift({
21 | type: 'paragraph',
22 | children: [{ type: 'text', value: 'Details' }],
23 | data: { hName: 'summary' },
24 | })
25 |
26 | data.hName = tagName
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/inferred-frontmatter.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root, Yaml } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkInferFrontmatter() {
8 | return (tree: Root) => {
9 | visit(tree, (node, _, parent) => {
10 | if (parent?.type !== 'root') return
11 |
12 | if (node.type === 'heading' && node.depth === 1) {
13 | if (node.children.length === 0) return
14 |
15 | const child = node.children[0]
16 | if (!('value' in child)) return
17 |
18 | const value = child.value
19 | const [, title, description] = value.includes('[')
20 | ? value.match(/(.*) \[(.*)\]/) || []
21 | : [undefined, JSON.stringify(value)]
22 |
23 | const frontmatterIndex = parent.children.findIndex((child) => child.type === 'yaml')
24 | const index = frontmatterIndex > 0 ? frontmatterIndex : 0
25 |
26 | const frontmatter = {
27 | ...(parent.children[frontmatterIndex] || {
28 | value: '',
29 | type: 'yaml',
30 | }),
31 | } as Yaml
32 | if (!frontmatter.value.includes('title')) frontmatter.value += `\ntitle: ${title}\n`
33 | if (!frontmatter.value.includes('description'))
34 | frontmatter.value += `\ndescription: ${description}\n`
35 |
36 | if (frontmatterIndex === -1) tree.children.unshift(frontmatter)
37 | else parent.children.splice(index, 1, frontmatter)
38 | }
39 | })
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/sponsors.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkSponsors() {
8 | return (tree: Root) => {
9 | visit(tree, (node, index, parent) => {
10 | if (node.type !== 'leafDirective') return
11 | if (node.name !== 'sponsors') return
12 | if (!index) return
13 | ;(parent?.children[index] as any).children.push({
14 | type: 'paragraph',
15 | data: {
16 | hName: 'div',
17 | hProperties: { 'data-sponsors': true },
18 | },
19 | })
20 | })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/steps.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import { h } from 'hastscript'
5 | import type { Heading, Root } from 'mdast'
6 | import { visit } from 'unist-util-visit'
7 |
8 | export function remarkSteps() {
9 | return (tree: Root) => {
10 | visit(tree, (node) => {
11 | if (node.type !== 'containerDirective') return
12 | if (node.name !== 'steps') return
13 |
14 | const data = node.data || (node.data = {})
15 | const tagName = 'div'
16 |
17 | node.attributes = {
18 | ...node.attributes,
19 | 'data-vocs-steps': 'true',
20 | }
21 |
22 | data.hName = tagName
23 | data.hProperties = h(tagName, node.attributes || {}).properties
24 |
25 | const depth = (node.children.find((child) => child.type === 'heading') as Heading)?.depth ?? 2
26 |
27 | let currentChild: any
28 | const children = []
29 | for (const child of node.children) {
30 | if (child.type === 'heading' && child.depth === depth) {
31 | if (currentChild && currentChild.children.length > 0) children.push(currentChild)
32 | currentChild = {
33 | type: 'paragraph',
34 | children: [],
35 | data: {
36 | hName: 'div',
37 | hProperties: {
38 | 'data-depth': depth,
39 | },
40 | },
41 | } as any
42 | }
43 | currentChild!.children.push(child)
44 | }
45 | children.push(currentChild)
46 |
47 | node.children = children
48 | })
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/strong-block.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkStrongBlock() {
8 | return (tree: Root) => {
9 | visit(tree, 'strong', (node, _, parent) => {
10 | if (!parent) return
11 | if (parent.type !== 'paragraph') return
12 | if (parent.children.length > 1) return
13 |
14 | parent.type = 'strong' as any
15 | parent.children = node.children
16 | })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/subheading.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkSubheading() {
8 | return (tree: Root) => {
9 | visit(tree, 'heading', (node, index, parent) => {
10 | if (!index) return
11 | if (node.depth !== 1) return
12 | if (node.children.length === 0) return
13 |
14 | const subheadingRegex = / \[(.*)\]$/
15 | const subheadingChild = node.children.find(
16 | (child) =>
17 | 'value' in child && typeof child.value === 'string' && child.value.match(subheadingRegex),
18 | ) as any
19 | const [match, subheading] = subheadingChild?.value?.match(subheadingRegex) ?? []
20 | if (subheadingChild) subheadingChild.value = subheadingChild?.value?.replace(match, '')
21 |
22 | // remove original heading
23 | parent?.children.splice(index, 1)
24 |
25 | const header = {
26 | type: 'paragraph',
27 | data: {
28 | hName: 'header',
29 | },
30 | children: [
31 | node,
32 | subheading
33 | ? {
34 | type: 'paragraph',
35 | children: [{ type: 'text', value: subheading }],
36 | data: {
37 | hName: 'div',
38 | hProperties: {
39 | role: 'doc-subtitle',
40 | },
41 | },
42 | }
43 | : undefined,
44 | ].filter(Boolean),
45 | } as any
46 | parent?.children.splice(index, 0, header)
47 | })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/vite/plugins/remark/twoslash.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import type { Root } from 'mdast'
5 | import { visit } from 'unist-util-visit'
6 |
7 | export function remarkTwoslash() {
8 | return (tree: Root) => {
9 | visit(tree, (node) => {
10 | if (node.type === 'code') {
11 | // Add extra new lines between multiple twoslash annotations (@log, @error, etc)
12 | // so that they can render correctly.
13 | node.value = node.value
14 | .replace(/(\/\/\s@.*:\s.*)\n(\/\/)/g, '$1\n\n$2')
15 | .replace(/(\/\/\s@.*:\s.*)\n(\/\/)/g, '$1\n\n$2')
16 | }
17 | })
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/vite/plugins/resolve-vocs-modules.ts:
--------------------------------------------------------------------------------
1 | import { extname, resolve } from 'node:path'
2 | import type { PluginOption } from 'vite'
3 |
4 | import type { ParsedConfig } from '../../config.js'
5 | import { resolveVocsConfig } from '../utils/resolveVocsConfig.js'
6 |
7 | export function resolveVocsModules(): PluginOption {
8 | let config: ParsedConfig
9 | return {
10 | name: 'resolve-vocs',
11 | async buildStart() {
12 | config = (await resolveVocsConfig()).config
13 | },
14 | transform(code_, id) {
15 | let code = code_
16 | if (id.startsWith(resolve(config.rootDir))) {
17 | if (['.js', '.jsx', '.ts', '.tsx', '.md', '.mdx'].includes(extname(id))) {
18 | code = code.replace(
19 | /import (.*) from ("|')vocs("|')/g,
20 | `import $1 from $2${resolve(import.meta.dirname, '../..')}$3`,
21 | )
22 | code = code.replace(
23 | /import (.*) from ("|')vocs\/components("|')/g,
24 | `import $1 from $2${resolve(import.meta.dirname, '../../components')}$3`,
25 | )
26 | }
27 | }
28 | return code
29 | },
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/vite/plugins/shiki/transformerEmptyLine.ts:
--------------------------------------------------------------------------------
1 | import type { ShikiTransformer } from 'shiki'
2 |
3 | export const transformerEmptyLine = (): ShikiTransformer => ({
4 | name: 'empty-line',
5 | line(hast) {
6 | const child = hast.children[0]
7 | if (child) return
8 | hast.properties['data-empty-line'] = true
9 | hast.children = [
10 | {
11 | type: 'text',
12 | value: ' ',
13 | },
14 | ]
15 | },
16 | })
17 |
--------------------------------------------------------------------------------
/src/vite/plugins/shiki/transformerLineNumbers.ts:
--------------------------------------------------------------------------------
1 | import type { ShikiTransformer } from 'shiki'
2 |
3 | export const transformerLineNumbers = (): ShikiTransformer => ({
4 | name: 'line-numbers',
5 | code(hast) {
6 | if (!this.options.meta?.__raw?.includes('showLineNumbers')) return
7 | hast.properties['data-line-numbers'] = true
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/src/vite/plugins/shiki/transformerShrinkIndent.ts:
--------------------------------------------------------------------------------
1 | import type { ShikiTransformer } from 'shiki'
2 |
3 | export const transformerShrinkIndent = (): ShikiTransformer => ({
4 | name: 'indent',
5 | span(hast, _, __, lineElement) {
6 | const child = hast.children[0]
7 | if (child.type !== 'text') return
8 | if (!child.value) return
9 | if (child.value.trim().length !== 0) return
10 | if (lineElement.children.length !== 0) return
11 | hast.children[0] = { type: 'text', value: child.value.replace(/\s\s/g, ' ') }
12 | },
13 | })
14 |
--------------------------------------------------------------------------------
/src/vite/plugins/shiki/transformerSplitIdentifiers.ts:
--------------------------------------------------------------------------------
1 | import type { ShikiTransformer } from 'shiki'
2 |
3 | export const transformerSplitIdentifiers = (): ShikiTransformer => ({
4 | name: 'split-identifiers',
5 | span(hast) {
6 | // only apply for twoslash code blocks
7 | if (!this.meta.twoslash) return
8 |
9 | const child = hast.children[0]
10 | if (child.type !== 'text') return
11 | if (child.value.trim().length === 0) return
12 | if (child.value.match(/\/\/ \[!/)) return
13 |
14 | let identifier = false
15 | let item = ''
16 | const items = []
17 |
18 | for (const char of child.value) {
19 | if (char.match(/\w/)) {
20 | if (!identifier) {
21 | items.push(item)
22 | item = ''
23 | }
24 | identifier = true
25 | item += char
26 | } else if (char.match(/\W/)) {
27 | if (identifier) {
28 | items.push(item)
29 | item = ''
30 | }
31 | identifier = false
32 | item += char
33 | }
34 | }
35 |
36 | if (item) items.push(item)
37 |
38 | hast.children = items.map((item) => ({ type: 'text', value: item }))
39 | },
40 | })
41 |
--------------------------------------------------------------------------------
/src/vite/plugins/shiki/transformerTagLine.ts:
--------------------------------------------------------------------------------
1 | import type { ShikiTransformer } from 'shiki'
2 |
3 | export const transformerTagLine = (): ShikiTransformer => ({
4 | name: 'tag-line',
5 | root(hast) {
6 | const lines = (hast.children[0] as any)?.children[0]?.children
7 | if (!lines) return
8 |
9 | for (let i = 0; i < lines.length; i++) {
10 | const line = lines[i]
11 | if (line.properties?.class.includes('twoslash-tag-line')) {
12 | lines.splice(i - 1, 0, line)
13 | lines.splice(i + 1, 1)
14 | if (i + 1 === lines.length) lines.splice(i, 1)
15 | }
16 | }
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/src/vite/plugins/shiki/transformerTitle.ts:
--------------------------------------------------------------------------------
1 | import type { ShikiTransformer } from 'shiki'
2 |
3 | const titleRegex = /title="(.*)"|\[(.*)\]/
4 |
5 | export const transformerTitle = (): ShikiTransformer => ({
6 | name: 'title',
7 | root(hast) {
8 | const titleMatch = this.options.meta?.__raw?.match(titleRegex)
9 | if (!titleMatch) return
10 |
11 | const title = titleMatch[1] || titleMatch[2]
12 | const child = hast.children[0] as any
13 | hast.children = [
14 | {
15 | ...child,
16 | properties: {
17 | ...child.properties,
18 | 'data-title': title,
19 | 'data-lang': this.options.lang,
20 | },
21 | },
22 | ]
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/src/vite/plugins/shiki/twoslasher.ts:
--------------------------------------------------------------------------------
1 | import { createTwoslasher } from 'twoslash'
2 | import * as cache_ from '../../utils/cache.js'
3 | import { hash } from '../../utils/hash.js'
4 |
5 | const twoslasher_ = createTwoslasher()
6 |
7 | export function twoslasher({ cacheDir }: { cacheDir?: string } = {}) {
8 | const cache = cache_.twoslash({ cacheDir })
9 |
10 | return (...parameters: Parameters): ReturnType => {
11 | const codeHash = hash(parameters[0])
12 | if (cache.get(codeHash)) return cache.get(codeHash)
13 | try {
14 | const twoslash = twoslasher_(...parameters)
15 | cache.set(codeHash, twoslash)
16 | return twoslash
17 | } catch (e) {
18 | if (!parameters[0].includes('@allowErrors')) throw e
19 | const error = e as Error
20 | const lines = parameters[0].split('\n')
21 | const line = lines.length - 1
22 | return {
23 | code: parameters[0],
24 | nodes: [
25 | {
26 | filename: '',
27 | level: 'error',
28 | type: 'error',
29 | code: 0,
30 | length: 100,
31 | start: 0,
32 | line,
33 | character: 0,
34 | text: error.message.replace('\n', ''),
35 | id: '',
36 | },
37 | ],
38 | // @ts-expect-error
39 | meta: {},
40 | queries: [],
41 | completions: [],
42 | errors: [],
43 | highlights: [],
44 | hovers: [],
45 | tags: [],
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/vite/plugins/virtual-config.ts:
--------------------------------------------------------------------------------
1 | import type { PluginOption } from 'vite'
2 |
3 | import { deserializeFunctionsStringified, serializeConfig } from '../../config.js'
4 | import { resolveVocsConfig } from '../utils/resolveVocsConfig.js'
5 |
6 | export function virtualConfig(): PluginOption {
7 | const virtualModuleId = 'virtual:config'
8 | const resolvedVirtualModuleId = `\0${virtualModuleId}`
9 |
10 | return {
11 | name: 'vocs-config',
12 | async configureServer(server) {
13 | const { configPath } = await resolveVocsConfig()
14 | if (configPath) {
15 | server.watcher.add(configPath)
16 | server.watcher.on('change', async (path) => {
17 | if (path !== configPath) return
18 | server.ws.send('vocs:config', (await resolveVocsConfig()).config)
19 | })
20 | }
21 | },
22 | resolveId(id) {
23 | if (id === virtualModuleId) return resolvedVirtualModuleId
24 | return
25 | },
26 | async load(id) {
27 | if (id === resolvedVirtualModuleId) {
28 | const { config } = await resolveVocsConfig()
29 | return `
30 | ${deserializeFunctionsStringified}
31 |
32 | export const config = deserializeFunctions(${serializeConfig(config)})`
33 | }
34 | return
35 | },
36 | handleHotUpdate() {
37 | // TODO: handle changes
38 | return
39 | },
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/vite/plugins/virtual-consumer-components.ts:
--------------------------------------------------------------------------------
1 | import { existsSync, readFileSync } from 'node:fs'
2 | import { resolve } from 'node:path'
3 | import type { PluginOption } from 'vite'
4 |
5 | import { resolveVocsConfig } from '../utils/resolveVocsConfig.js'
6 |
7 | export function virtualConsumerComponents(): PluginOption {
8 | const virtualModuleId = 'virtual:consumer-components'
9 | const resolvedVirtualModuleId = `\0${virtualModuleId}`
10 |
11 | return {
12 | name: 'routes',
13 | resolveId(id) {
14 | if (id === virtualModuleId) return resolvedVirtualModuleId
15 | return
16 | },
17 | async load(id) {
18 | if (id !== resolvedVirtualModuleId) return
19 |
20 | const { config } = await resolveVocsConfig()
21 | const { rootDir } = config
22 | return `
23 | ${exportComponent(resolve(rootDir, 'layout.tsx'), 'Layout')}
24 | ${exportComponent(resolve(rootDir, 'footer.tsx'), 'Footer')}
25 | ${exportComponent(resolve(rootDir, 'layout.tsx'), 'TopNavEnd', { named: true })}
26 | `
27 | },
28 | }
29 | }
30 |
31 | function exportComponent(path: string, name: string, { named = false }: { named?: boolean } = {}) {
32 | const exists =
33 | existsSync(path) && new RegExp(`export(.*)${name}`).test(readFileSync(path, 'utf-8'))
34 | if (exists) return `export { ${!named ? `default as ${name}` : name} } from "${path}";`
35 | return `export const ${name} = ({ children }) => children;`
36 | }
37 |
--------------------------------------------------------------------------------
/src/vite/preview.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { serve } from '@hono/node-server'
3 | import { Hono } from 'hono'
4 | import { compress } from 'hono/compress'
5 |
6 | import { resolveVocsConfig } from './utils/resolveVocsConfig.js'
7 | import { serveStatic } from './utils/serveStatic.js'
8 |
9 | type PreviewParameters = {
10 | outDir?: string
11 | }
12 |
13 | export async function preview({ outDir = 'dist' }: PreviewParameters = {}) {
14 | const { config } = await resolveVocsConfig()
15 | const { basePath, rootDir } = config
16 |
17 | const app = new Hono()
18 |
19 | app.use('*', compress())
20 | app.use(
21 | '/*',
22 | serveStatic({
23 | root: resolve(rootDir, outDir),
24 | rewriteRequestPath(path) {
25 | return basePath ? path.replace(basePath!, '') : path
26 | },
27 | }),
28 | )
29 |
30 | return new Promise & { port: number }>((res) => {
31 | async function createServer(port = 4173) {
32 | process.on('uncaughtException', (err: any) => {
33 | if (err.code !== 'EADDRINUSE') throw err
34 | process.removeAllListeners()
35 | createServer(port + 1)
36 | })
37 |
38 | const server = serve({
39 | fetch: app.fetch,
40 | port,
41 | }).on('listening', () => {
42 | res(Object.assign(server, { port }))
43 | })
44 | }
45 |
46 | createServer()
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/src/vite/utils/cache.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { default as fs } from 'fs-extra'
3 |
4 | export const search = create('search')
5 | export const twoslash = create('twoslash')
6 |
7 | export function create(key: string) {
8 | return ({
9 | cacheDir = resolve(import.meta.dirname, '../.vocs/cache'),
10 | }: { cacheDir?: string } = {}) => {
11 | const pathname = (k: string) => resolve(cacheDir, `${key}${k ? `.${k}` : ''}.json`)
12 | return {
13 | get(k: string) {
14 | let data = fs.readJSONSync(pathname(k), { throws: false })
15 | data = JSON.parse(data ?? '{}')
16 | return data.value
17 | },
18 | set(k: string, value: v) {
19 | fs.ensureDirSync(cacheDir)
20 | fs.writeJSONSync(pathname(k), JSON.stringify({ value }))
21 | },
22 | }
23 | }
24 | }
25 |
26 | export function clear({
27 | cacheDir = resolve(import.meta.dirname, '../.vocs/cache'),
28 | }: { cacheDir?: string } = {}) {
29 | if (!fs.existsSync(cacheDir)) return
30 | fs.rmSync(cacheDir, { recursive: true })
31 | }
32 |
--------------------------------------------------------------------------------
/src/vite/utils/getGitTimestamp.ts:
--------------------------------------------------------------------------------
1 | import { basename, dirname } from 'node:path'
2 | import { spawn } from 'cross-spawn'
3 | import fs from 'fs-extra'
4 |
5 | const cache = new Map()
6 |
7 | export function getGitTimestamp(file: string) {
8 | const cached = cache.get(file)
9 | if (cached) return cached
10 |
11 | return new Promise((resolve, reject) => {
12 | const cwd = dirname(file)
13 | if (!fs.existsSync(cwd)) return resolve(0)
14 | const fileName = basename(file)
15 | const child = spawn('git', ['log', '-1', '--pretty="%ai"', fileName], {
16 | cwd,
17 | })
18 | let output = ''
19 | child.stdout.on('data', (d) => (output += String(d)))
20 | child.on('close', () => {
21 | const timestamp = +new Date(output)
22 | cache.set(file, timestamp)
23 | resolve(timestamp)
24 | })
25 | child.on('error', reject)
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/src/vite/utils/hash.ts:
--------------------------------------------------------------------------------
1 | import { createHash } from 'node:crypto'
2 |
3 | export function hash(text: Buffer | string, length?: number): string {
4 | let hash = createHash('sha256').update(text).digest('hex')
5 | if (length) hash = hash.substring(0, length)
6 | return hash
7 | }
8 |
--------------------------------------------------------------------------------
/src/vite/utils/html.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from 'react'
2 | import { renderToStaticMarkup, renderToString } from 'react-dom/server'
3 | import type { Config } from '../../config.js'
4 |
5 | export async function toMarkup(parameters: {
6 | body: ReactNode
7 | config: Config
8 | head: ReactNode
9 | location: string
10 | template: string
11 | }) {
12 | const { body, config, location, template } = parameters
13 | const { theme } = config
14 |
15 | const preHtml = renderToStaticMarkup(
16 |
17 |
18 | {body}
19 | ,
20 | )
21 | const preHead = preHtml.match(/([\s\S]*?)<\/head>/)?.[1]
22 |
23 | const configHead = await (async () => {
24 | if (typeof config.head === 'function') return await config.head({ path: location })
25 | if (typeof config.head === 'object') {
26 | const entry = Object.entries(config.head)
27 | .reverse()
28 | .find(([key]) => location.startsWith(key))
29 | return entry?.[1]
30 | }
31 | return config.head
32 | })()
33 | const head = renderToString(
34 | <>
35 | {parameters.head}
36 | {configHead}
37 | >,
38 | )
39 |
40 | let html = template.replace('', `${head}${preHead}`)
41 | html = html.replace('', renderToString(body).replace(preHead ?? '', ''))
42 |
43 | const match = html.match(/property="og:image" content="(.*)"/)
44 | if (match?.[1]) {
45 | html = html.replace(
46 | /property="og:image" content="(.*)"/,
47 | `property="og:image" content="${match[1].replace(/&/g, '&')}"`,
48 | )
49 | }
50 | if (theme?.colorScheme && theme?.colorScheme !== 'system')
51 | html = html.replace('lang="en"', `lang="en" class="${theme.colorScheme}"`)
52 |
53 | return html
54 | }
55 |
--------------------------------------------------------------------------------
/src/vite/utils/resolveOutDir.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { vercelBuildOutputDir } from './vercel.js'
3 |
4 | export function resolveOutDir(rootDir: string, outDir?: string) {
5 | if (typeof outDir === 'undefined') {
6 | // If we're in a Vercel environment, use the Vercel Build Output directory.
7 | // https://vercel.com/docs/build-output-api/v3
8 | if (process.env.VERCEL) return resolve(vercelBuildOutputDir, 'static')
9 | }
10 | return resolve(rootDir, outDir ?? 'dist')
11 | }
12 |
--------------------------------------------------------------------------------
/src/vite/utils/slash.ts:
--------------------------------------------------------------------------------
1 | export function slash(p: string): string {
2 | return p.replace(/\\/g, '/')
3 | }
4 |
--------------------------------------------------------------------------------
/src/vite/utils/vercel.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { default as fs } from 'fs-extra'
3 |
4 | export const vercelBuildOutputDir = resolve(process.cwd(), '.vercel/output')
5 |
6 | export function writeBuildOutputConfig() {
7 | fs.writeJsonSync(resolve(vercelBuildOutputDir, 'config.json'), { version: 3 })
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
3 | "extends": "./tsconfig.base.json",
4 | "include": ["src"],
5 | "exclude": [
6 | "src/**/*.test.ts",
7 | "src/**/*.test-d.ts"
8 | ],
9 | "compilerOptions": {
10 | "declaration": true,
11 | "declarationMap": true,
12 | "outDir": "./src/_lib",
13 | "sourceMap": true,
14 | "rootDir": "./src"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "include": ["playgrounds", "scripts", "src", "vocs.config.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {
4 | "source": "/api/(.*)",
5 | "destination": "/api"
6 | },
7 | {
8 | "source": "/:match*.html",
9 | "destination": "/:match"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------