Skip to content

Commit

Permalink
feat: headless implement docs content (#2)
Browse files Browse the repository at this point in the history
* setup

* basic usage

* code highlight

* example component

* fix

* fix typecheck

* improve example like real usage

* fix reactive
  • Loading branch information
unnoq authored Mar 22, 2024
1 parent 76b9987 commit 9b0c085
Show file tree
Hide file tree
Showing 16 changed files with 1,894 additions and 93 deletions.
4 changes: 4 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?

.velite

public/static
71 changes: 71 additions & 0 deletions apps/web/components/component-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Suspense, lazy } from 'react'
import { codeToHtml } from 'shiki'

const exampleComponents = import.meta.glob('../examples/**/*.tsx') as {
[key: string]: () => Promise<{
default: React.ComponentType
}>
}

const exampleCodes = import.meta.glob('../examples/**/*.tsx', {
query: '?raw',
import: 'default',
}) as {
[key: string]: () => Promise<string>
}

export function ComponentExample({ name }: { name: string }) {
const path = `../examples/${name}.tsx`

return (
<div>
<ComponentPreview path={path} />
<ComponentCode path={path} />
</div>
)
}

function ComponentPreview(props: { path: string }) {
const loadComponent = exampleComponents[props.path]

if (!loadComponent) {
throw new Error(`Component does not exists on [${props.path}]`)
}

const Component = lazy(loadComponent)

return (
// TODO
<Suspense fallback="loading...">
<Component />
</Suspense>
)
}

function ComponentCode(props: { path: string }) {
const loadCode = exampleCodes[props.path]

if (!loadCode) {
throw new Error(`Component does not exists on [${props.path}]`)
}

const Code = lazy(async () => {
const code = await loadCode()
const html = await codeToHtml(code, {
lang: 'tsx',
theme: 'github-dark',
})
return {
default: () => {
return <div dangerouslySetInnerHTML={{ __html: html }} />
},
}
})

return (
// TODO
<Suspense fallback="loading...">
<Code />
</Suspense>
)
}
22 changes: 22 additions & 0 deletions apps/web/components/mdx-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentExample } from './component-example'
import '@web/styles/mdx.css'
import * as runtime from 'react/jsx-runtime'

const mdxComponents = {
ComponentExample,
}

const useMDXComponent = (code: string) => {
const fn = new Function(code)
return fn({ ...runtime }).default
}

interface MdxProps {
code: string
components?: Record<string, React.ComponentType>
}

export const MDXContent = ({ code, components }: MdxProps) => {
const Component = useMDXComponent(code)
return <Component components={{ ...mdxComponents, ...components }} />
}
13 changes: 13 additions & 0 deletions apps/web/content/docs/ui/button.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Button component
description: Button
publishAt: 1992-02-25 13:22
---

The content of button component

```js
import { jsx } from 'react/jsx-runtime'
```

<ComponentExample name="ui/button" />
5 changes: 5 additions & 0 deletions apps/web/examples/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Button } from '@dinui/react/button'

export default function ButtonExample() {
return <Button>hehe hoho</Button>
}
11 changes: 10 additions & 1 deletion apps/web/main.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { docs } from './.velite/index'
import { MainErrorBoundary, MainLayout } from './layouts/main.tsx'
import './styles/globals.css'
import { ViteReactSSG } from 'vite-react-ssg'
Expand All @@ -11,9 +12,17 @@ export const routes: RouteRecord[] = [
children: [
{
index: true,
lazy: () => import('./pages/home.tsx'),
lazy: () => import('./pages/home'),
entry: './pages/home.tsx',
},
{
path: 'docs/:category/:name',
lazy: () => import('./pages/doc-detail'),
entry: './pages/doc-detail.tsx',
getStaticPaths() {
return docs.map((doc) => 'docs/' + doc.relativePath)
},
},
],
},
]
Expand Down
11 changes: 8 additions & 3 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite-react-ssg dev",
"build": "vite-react-ssg build",
"dev": "concurrently 'velite dev' 'vite-react-ssg dev'",
"build": "velite build && vite-react-ssg build",
"preview": "vite preview --port 3000",
"type:check": "tsc --noEmit"
"type:check": "velite build && tsc --noEmit"
},
"dependencies": {
"@dinui/react": "workspace:^",
Expand All @@ -22,8 +22,13 @@
"autoprefixer": "^10.4.19",
"critters": "^0.0.22",
"postcss": "^8.4.38",
"rehype-autolink-headings": "^7.1.0",
"rehype-pretty-code": "^0.13.0",
"rehype-slug": "^6.0.0",
"shiki": "^1.2.0",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.2",
"velite": "0.1.0-beta.12",
"vite": "^5.2.0",
"vite-react-ssg": "^0.6.0"
}
Expand Down
25 changes: 25 additions & 0 deletions apps/web/pages/doc-detail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { docs } from '@web/.velite'
import { MDXContent } from '@web/components/mdx-content'
import { useLoaderData, type LoaderFunctionArgs } from 'react-router-dom'

export function loader({ params }: LoaderFunctionArgs) {
const doc = docs.find((d) => `${params['category']}/${params['name']}` === d.relativePath)

if (!doc) {
throw new Error(`Doc does not exist: ${params['*']}`)
}

return {
doc,
}
}

export function Component() {
const { doc } = useLoaderData() as Awaited<ReturnType<typeof loader>>

return (
<div className="py-8 container">
<MDXContent code={doc.code} />
</div>
)
}
2 changes: 1 addition & 1 deletion apps/web/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button } from '@react-ui/ui/button'
import { Button } from '@dinui/react/button'

export function Component() {
return (
Expand Down
35 changes: 35 additions & 0 deletions apps/web/styles/mdx.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[data-rehype-pretty-code-figure] pre {
@apply px-0;
}

[data-rehype-pretty-code-figure] code {
@apply text-sm !leading-loose md:text-base border-0 p-0;
}

[data-rehype-pretty-code-figure] code[data-line-numbers] {
counter-reset: line;
}

[data-rehype-pretty-code-figure] code[data-line-numbers] > [data-line]::before {
counter-increment: line;
content: counter(line);
@apply mr-4 inline-block w-4 text-right text-gray-500;
}

[data-rehype-pretty-code-figure] [data-line] {
@apply border-l-2 border-l-transparent px-3;
}

[data-rehype-pretty-code-figure] [data-highlighted-line] {
background: rgba(200, 200, 255, 0.1);
@apply border-l-blue-400;
}

[data-rehype-pretty-code-figure] [data-highlighted-chars] {
@apply rounded bg-zinc-600/50;
box-shadow: 0 0 0 4px rgb(82 82 91 / 0.5);
}

[data-rehype-pretty-code-figure] [data-chars-id] {
@apply border-b-2 p-1 shadow-none;
}
7 changes: 4 additions & 3 deletions apps/web/tailwind.config.js → apps/web/tailwind.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/** @type {import('tailwindcss').Config} */
import type { Config } from 'tailwindcss'

export default {
content: ['./layouts/**.tsx', './pages/**.tsx', './components/**.tsx'],
content: ['./layouts/**.tsx', './pages/**.tsx', './components/**.tsx', './examples/**.tsx'],
theme: {
extend: {},
},
plugins: [],
}
} satisfies Config
3 changes: 2 additions & 1 deletion apps/web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,

"paths": {
"@web/*": ["./*"],
"@react-ui/*": ["../../packages/react-ui/*"]
"@dinui/react/*": ["../../packages/react-ui/ui/*"]
}
},
"exclude": ["dist", "**.js"],
Expand Down
50 changes: 50 additions & 0 deletions apps/web/velite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypePrettyCode from 'rehype-pretty-code'
import rehypeSlug from 'rehype-slug'
import { defineCollection, defineConfig, s } from 'velite'

const docs = defineCollection({
name: 'Post',
pattern: 'docs/**/*.mdx',
schema: s
.object({
title: s.string().min(1).max(99),
description: s.string().min(1),
publishAt: s.isodate(),

toc: s.toc(),
code: s.mdx(),
path: s.path(),
})
.transform((data) => {
const relativePath = data.path.replace('docs/', '')
const pathName = '/' + data.path

return {
...data,
relativePath,
pathName,
}
}),
})

export default defineConfig({
collections: {
docs,
},
mdx: {
rehypePlugins: [
rehypeSlug,
[rehypePrettyCode, { theme: 'github-dark' }],
[
rehypeAutolinkHeadings,
{
properties: {
className: ['subheading-anchor'],
ariaLabel: 'Link to section',
},
},
],
],
},
})
3 changes: 2 additions & 1 deletion apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export default defineConfig({
},
resolve: {
alias: {
'@react-ui': resolve(__dirname, '../../packages/react-ui'),
'@web': resolve(__dirname, './'),
'@dinui/react': resolve(__dirname, '../../packages/react-ui/ui'),
},
},
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"concurrently": "^8.2.2",
"env-cmd": "^10.1.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
Expand Down
Loading

0 comments on commit 9b0c085

Please sign in to comment.