mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
init(docs): create docs site template with Fumapress and Waku
This commit is contained in:
1 parent
49a52b138f
commit
1aaab66971
18 files changed
+3059
No files matched your search
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
.source
|
||||
.vercel
|
||||
@@ -0,0 +1,85 @@
|
||||
# docs
|
||||
|
||||
A [Fumapress](https://press.fumadocs.dev) (Fumadocs + Waku) docs site with
|
||||
typography by [shadcn/typeset](https://ui.shadcn.com/docs/typeset) — one CSS
|
||||
file you own.
|
||||
|
||||
## Getting started
|
||||
|
||||
Requires **Node.js 22+**.
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun dev # http://localhost:3000
|
||||
```
|
||||
|
||||
## Your typeset
|
||||
|
||||
| Control | Choice |
|
||||
| ------- | ------ |
|
||||
| Heading | DM Sans |
|
||||
| Body | Instrument Sans |
|
||||
| Mono | JetBrains Mono |
|
||||
| Size | 16px |
|
||||
| Leading | Loose (1.9) |
|
||||
| Flow | Compact (1em) |
|
||||
| Measure | 80ch |
|
||||
|
||||
- `src/typeset.css` is the stylesheet from the
|
||||
[typeset builder](https://ui.shadcn.com/typeset). It lives in your project —
|
||||
edit it directly, or regenerate it from the builder any time.
|
||||
- `src/app.css` holds your presets. `.typeset-docs` carries the choices
|
||||
above; `.typeset-compact` is a tighter preset for chat-like UI. Rhythm is
|
||||
three variables: `--typeset-size`, `--typeset-leading`, `--typeset-flow`.
|
||||
- `typeset-prompt.md` is an agent-ready prompt with the choices above baked
|
||||
in (like the builder's Prompt tab). Paste it into your coding agent to wire
|
||||
typeset into the surfaces that render markdown.
|
||||
|
||||
Wrap any rendered markdown/HTML:
|
||||
|
||||
```tsx
|
||||
<article className="typeset typeset-docs typeset-measure">{page}</article>
|
||||
<div className="typeset typeset-compact">{message}</div>
|
||||
```
|
||||
|
||||
Opt components out with `not-typeset`:
|
||||
|
||||
```tsx
|
||||
<div className="typeset typeset-docs">
|
||||
<p>Styled prose.</p>
|
||||
<Card className="not-typeset">Untouched component.</Card>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Fumadocs prose seam
|
||||
|
||||
Fumadocs UI ships its own `prose` styles for docs page bodies. If you apply
|
||||
`typeset` to the same container, both systems will add spacing. This
|
||||
template zeroes out `margin-block-end` inside `.typeset.prose` (see
|
||||
`src/app.css`) so flow comes only from typeset's `margin-block-start`.
|
||||
|
||||
To apply the typeset classes to docs pages, customize the page layout in
|
||||
`press.config.tsx` — see the
|
||||
[Fumapress layouts docs](https://press.fumadocs.dev/docs/layouts).
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
├── waku.config.ts # Waku/Vite plugins: press(), mdx(), tailwindcss()
|
||||
├── source.config.ts # Fumadocs MDX content source (content/docs)
|
||||
├── press.config.tsx # Site config, adapters, plugins
|
||||
├── typeset-prompt.md # Agent prompt with your typeset picks baked in
|
||||
├── content/docs/ # Your MDX pages
|
||||
└── src/
|
||||
├── app.css # Tailwind + presets + your typeset presets
|
||||
└── typeset.css # shadcn/typeset — yours to edit
|
||||
```
|
||||
|
||||
## Deploying static
|
||||
|
||||
Fumapress builds static + dynamic routes by default. For a pure CDN deploy,
|
||||
force static mode in `press.config.tsx`:
|
||||
|
||||
```tsx
|
||||
export default defineConfig({ mode: "static", /* ... */ });
|
||||
```
|
||||
+1732
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Hello World
|
||||
description: A Fumapress site styled with shadcn/typeset
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This page renders through Fumadocs MDX and is styled by the `typeset typeset-docs` container — headings, paragraphs, lists, tables, and code all inherit their rhythm from three variables: `--typeset-size`, `--typeset-leading`, and `--typeset-flow`.
|
||||
|
||||
### Why one CSS file?
|
||||
|
||||
- The file lives in your repo — edit rules directly, no plugin config
|
||||
- Spacing flows one direction (`margin-block-start` only), so streamed content never restyles earlier blocks
|
||||
- Zero-specificity `:where()` guards mean Tailwind utilities always win
|
||||
|
||||
```ts
|
||||
// Inline overrides still work inside a typeset container
|
||||
export const rhythm = {
|
||||
size: "15px",
|
||||
leading: 1.75,
|
||||
flow: "1.5em",
|
||||
};
|
||||
```
|
||||
|
||||
> Tune a tighter preset for chat, a roomier one for docs — same file, different rhythm.
|
||||
|
||||
| Control | Purpose | Default |
|
||||
| ------------------ | --------------------- | -------- |
|
||||
| `--typeset-size` | Base text size | `1em` |
|
||||
| `--typeset-leading`| Line height | `1.75` |
|
||||
| `--typeset-flow` | Space between blocks | `1.25em` |
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "waku dev",
|
||||
"build": "waku build",
|
||||
"start": "waku start",
|
||||
"types:check": "fumadocs-mdx && tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "latest",
|
||||
"@fontsource-variable/dm-sans": "latest",
|
||||
"@fontsource-variable/instrument-sans": "latest",
|
||||
"@fontsource-variable/jetbrains-mono": "latest",
|
||||
"fumadocs-core": "latest",
|
||||
"fumadocs-mdx": "latest",
|
||||
"fumadocs-ui": "npm:@fumadocs/base-ui@latest",
|
||||
"fumapress": "latest",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-server-dom-webpack": "^19.2.0",
|
||||
"vercel": "^55.0.0",
|
||||
"waku": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { defineConfig } from "fumapress";
|
||||
import { fumadocsMdx } from "fumapress/adapters/mdx";
|
||||
import { flexsearchPlugin } from "fumapress/plugins/flexsearch";
|
||||
import { llmsPlugin } from "fumapress/plugins/llms.txt";
|
||||
import { docs } from "./.source/server";
|
||||
|
||||
export default defineConfig({
|
||||
site: {
|
||||
name: "Docs",
|
||||
baseUrl: import.meta.env.DEV
|
||||
? "http://localhost:3000"
|
||||
: "https://docs.example.com",
|
||||
git: {
|
||||
user: "your-github-user",
|
||||
branch: "main",
|
||||
repo: "docs",
|
||||
},
|
||||
},
|
||||
content: {
|
||||
docs: docs.toFumadocsSource(),
|
||||
},
|
||||
meta: {
|
||||
root() {
|
||||
return <meta property="og:type" content="website" />;
|
||||
},
|
||||
},
|
||||
})
|
||||
.plugins(flexsearchPlugin(), llmsPlugin())
|
||||
.adapters(fumadocsMdx());
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
||||
|
||||
export const docs = defineDocs({
|
||||
dir: "content/docs",
|
||||
docs: {
|
||||
// Required by fumapress' llms.txt plugin (getText("processed")).
|
||||
postprocess: { includeProcessedMarkdown: true },
|
||||
},
|
||||
});
|
||||
|
||||
export default defineConfig();
|
||||
@@ -0,0 +1,65 @@
|
||||
@import "tailwindcss";
|
||||
@import "fumadocs-ui/css/neutral.css";
|
||||
@import "fumadocs-ui/css/preset.css";
|
||||
@import "fumapress/css/preset.css";
|
||||
|
||||
/* Fonts — self-hosted via Fontsource, referenced by the typeset presets. */
|
||||
@import "@fontsource-variable/dm-sans";
|
||||
@import "@fontsource-variable/instrument-sans";
|
||||
@import "@fontsource-variable/jetbrains-mono";
|
||||
|
||||
/* shadcn/typeset — one CSS file you own.
|
||||
Regenerate it any time at https://ui.shadcn.com/typeset */
|
||||
@import "./typeset.css";
|
||||
|
||||
:root {
|
||||
--font-dm-sans: "DM Sans Variable", sans-serif;
|
||||
--font-instrument-sans: "Instrument Sans Variable", sans-serif;
|
||||
--font-jetbrains-mono: "JetBrains Mono Variable", monospace;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--default-font-family: var(--font-instrument-sans);
|
||||
--font-heading: var(--font-dm-sans);
|
||||
--font-mono: var(--font-jetbrains-mono);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
Typeset presets — tune rhythm per context.
|
||||
Three controls: size, leading, flow.
|
||||
Built with https://ui.shadcn.com/typeset
|
||||
--------------------------------------------------------- */
|
||||
|
||||
.typeset-docs {
|
||||
--typeset-font-body: var(--font-instrument-sans);
|
||||
--typeset-font-heading: var(--font-dm-sans);
|
||||
--typeset-font-mono: var(--font-jetbrains-mono);
|
||||
--typeset-size: 16px;
|
||||
--typeset-leading: 1.9;
|
||||
--typeset-flow: 1em;
|
||||
}
|
||||
|
||||
/* Tighter rhythm, e.g. for changelog callouts or chat-like UI */
|
||||
.typeset-compact {
|
||||
--typeset-leading: 1.6;
|
||||
--typeset-flow: 1em;
|
||||
}
|
||||
|
||||
/* Measure (80ch) — the builder puts max-width on the wrapper,
|
||||
not in typeset.css. Add this class next to `typeset` when your
|
||||
layout doesn't already constrain line length. */
|
||||
.typeset-measure {
|
||||
max-width: 37em;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
Fumadocs interop:
|
||||
Fumadocs UI ships its own `prose` styling for page bodies.
|
||||
When a container is both, let Typeset own the rhythm by
|
||||
disabling the prose margins that would double up.
|
||||
Keep interactive Fumadocs components (tabs, callouts,
|
||||
accordions) out of Typeset with `not-typeset`.
|
||||
--------------------------------------------------------- */
|
||||
.typeset.prose :where(*) {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
* shadcn/typeset
|
||||
* https://ui.shadcn.com/docs/typeset.
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
.typeset {
|
||||
--typeset-font-body: inherit;
|
||||
--typeset-font-heading: var(--font-heading);
|
||||
--typeset-font-mono: var(--font-mono);
|
||||
--typeset-size: 1em;
|
||||
--typeset-leading: 1.75;
|
||||
--typeset-flow: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.typeset {
|
||||
font-family: var(--typeset-font-body);
|
||||
font-size: calc(var(--typeset-size) * 1.125);
|
||||
line-height: var(--typeset-leading);
|
||||
color: var(--color-foreground, currentColor);
|
||||
overflow-wrap: break-word;
|
||||
margin-trim: block-start;
|
||||
|
||||
@media (min-width: 48rem), print {
|
||||
font-size: var(--typeset-size);
|
||||
}
|
||||
|
||||
--typeset-muted: var(
|
||||
--color-muted-foreground,
|
||||
color-mix(in oklab, currentColor 60%, transparent)
|
||||
);
|
||||
--typeset-rule: var(
|
||||
--color-border,
|
||||
color-mix(in oklab, currentColor 20%, transparent)
|
||||
);
|
||||
}
|
||||
|
||||
.typeset
|
||||
*:not(
|
||||
:where(
|
||||
.not-typeset,
|
||||
[data-not-typeset],
|
||||
.not-typeset *,
|
||||
[data-not-typeset] *
|
||||
)
|
||||
) {
|
||||
/* Paragraphs. */
|
||||
&:where(p) {
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
/* Headings. */
|
||||
&:where(h1, h2, h3, h4, h5, h6) {
|
||||
color: var(--color-foreground, currentColor);
|
||||
font-family: var(--typeset-font-heading);
|
||||
font-weight: 600;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
&:where(h1) {
|
||||
font-size: 1.75em;
|
||||
line-height: 1.3;
|
||||
margin-block-start: var(--typeset-flow);
|
||||
}
|
||||
&:where(h2) {
|
||||
font-size: 1.25em;
|
||||
line-height: 1.4;
|
||||
margin-block-start: calc(var(--typeset-flow) * 1.4);
|
||||
}
|
||||
&:where(h3) {
|
||||
font-size: 1.125em;
|
||||
line-height: 1.45;
|
||||
margin-block-start: var(--typeset-flow);
|
||||
}
|
||||
&:where(h4) {
|
||||
font-size: 1em;
|
||||
line-height: 1.5;
|
||||
margin-block-start: var(--typeset-flow);
|
||||
}
|
||||
&:where(h5) {
|
||||
font-size: 0.875em;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
color: var(--typeset-muted);
|
||||
margin-block-start: calc(var(--typeset-flow) / 0.875);
|
||||
}
|
||||
&:where(h6) {
|
||||
font-size: 0.8125em;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--typeset-muted);
|
||||
margin-block-start: calc(var(--typeset-flow) / 0.8125);
|
||||
}
|
||||
/* Anchors. */
|
||||
&:where([id]) {
|
||||
scroll-margin-block-start: var(--typeset-flow);
|
||||
}
|
||||
|
||||
/* Headings own the space below them. */
|
||||
&:where(h1 + *, h2 + *, h3 + *, h4 + *, h5 + *, h6 + *) {
|
||||
margin-block-start: 1em;
|
||||
}
|
||||
&:where(:is(h1, h2, h3, h4) :is(code)) {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Links. */
|
||||
&:where(a) {
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-color: color-mix(in oklab, currentColor 30%, transparent);
|
||||
}
|
||||
&:where(a:hover) {
|
||||
text-decoration-color: currentColor;
|
||||
}
|
||||
&:where(a:focus-visible) {
|
||||
outline: 2px solid var(--color-ring, currentColor);
|
||||
outline-offset: 2px;
|
||||
border-radius: 0.125em;
|
||||
}
|
||||
&:where(:is(h1, h2, h3, h4, h5, h6) :is(a)) {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/* Inline semantics. */
|
||||
&:where(strong, b) {
|
||||
font-weight: 600;
|
||||
}
|
||||
&:where(:is(h1, h2, h3, h4) :is(strong, b)) {
|
||||
font-weight: 600;
|
||||
}
|
||||
&:where(mark) {
|
||||
background-color: color-mix(
|
||||
in oklab,
|
||||
oklch(0.86 0.17 95) 40%,
|
||||
transparent
|
||||
);
|
||||
color: inherit;
|
||||
padding: 0.05em 0.15em;
|
||||
border-radius: 0.2em;
|
||||
}
|
||||
&:where(abbr[title]) {
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 0.2em;
|
||||
cursor: help;
|
||||
}
|
||||
&:where(del, s) {
|
||||
color: var(--typeset-muted);
|
||||
text-decoration-line: line-through;
|
||||
}
|
||||
&:where(sup, sub) {
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
&:where(sup) {
|
||||
top: -0.5em;
|
||||
}
|
||||
&:where(sub) {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
&:where(sup a) {
|
||||
text-decoration-line: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Lists. */
|
||||
&:where(ul) {
|
||||
list-style-type: disc;
|
||||
}
|
||||
&:where(ol) {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
&:where(ul ul) {
|
||||
list-style-type: circle;
|
||||
}
|
||||
&:where(ul ul ul) {
|
||||
list-style-type: square;
|
||||
}
|
||||
&:where(ol[type="a" i]) {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
&:where(ol[type="i" i]) {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
&:where(ol[type="A" s]) {
|
||||
list-style-type: upper-alpha;
|
||||
}
|
||||
&:where(ol[type="I" s]) {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
&:where(ul, ol) {
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
padding-inline-start: 1.5em;
|
||||
}
|
||||
&:where(li) {
|
||||
margin-block-start: 0.5em;
|
||||
padding-inline-start: 0.4em;
|
||||
}
|
||||
&:where(li > p, li > ul, li > ol) {
|
||||
margin-block-start: 0.5em;
|
||||
}
|
||||
&:where(ul > li)::marker {
|
||||
color: var(--typeset-muted);
|
||||
}
|
||||
&:where(ol > li)::marker {
|
||||
color: var(--typeset-muted);
|
||||
}
|
||||
|
||||
/* GFM task lists. */
|
||||
&:where(ul.contains-task-list) {
|
||||
list-style-type: none;
|
||||
padding-inline-start: 0.25em;
|
||||
}
|
||||
&:where(li.task-list-item > input[type="checkbox"]) {
|
||||
margin-inline-end: 0.5em;
|
||||
vertical-align: -0.1em;
|
||||
accent-color: var(--color-primary, currentColor);
|
||||
}
|
||||
|
||||
/* Disclosures. */
|
||||
&:where(details) {
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
&:where(summary) {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
&:where(summary)::marker {
|
||||
color: var(--typeset-muted);
|
||||
}
|
||||
|
||||
/* Keyboard keys. */
|
||||
&:where(kbd) {
|
||||
font-family: inherit;
|
||||
font-size: 0.85em;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--typeset-rule);
|
||||
border-block-end-width: 2px;
|
||||
border-radius: min(calc(var(--radius, 0.5em) * 0.6), 0.35em);
|
||||
padding: 0.0625em 0.35em;
|
||||
}
|
||||
|
||||
/* Definition lists. */
|
||||
&:where(dl) {
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
&:where(dt) {
|
||||
font-weight: 500;
|
||||
margin-block-start: 1em;
|
||||
}
|
||||
&:where(dt + dt) {
|
||||
margin-block-start: 0.25em;
|
||||
}
|
||||
&:where(dd) {
|
||||
margin-block-start: 0.25em;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 1em;
|
||||
color: var(--typeset-muted);
|
||||
}
|
||||
|
||||
/* Inline code. */
|
||||
&:where(:not(pre) > code) {
|
||||
background-color: var(
|
||||
--color-muted,
|
||||
color-mix(in oklab, currentColor 8%, transparent)
|
||||
);
|
||||
font-family: var(--typeset-font-mono);
|
||||
font-size: 0.85em;
|
||||
border-radius: min(calc(var(--radius, 0.5em) * 0.6), 0.35em);
|
||||
padding: 0.125em 0.3em;
|
||||
}
|
||||
|
||||
/* Code blocks. */
|
||||
&:where(pre) {
|
||||
background-color: var(
|
||||
--color-muted,
|
||||
color-mix(in oklab, currentColor 8%, transparent)
|
||||
);
|
||||
font-family: var(--typeset-font-mono);
|
||||
font-size: 0.875em;
|
||||
line-height: 1.5;
|
||||
tab-size: 2;
|
||||
direction: ltr;
|
||||
border-radius: var(--radius, 0.5em);
|
||||
padding: 0.75em 1em;
|
||||
overflow-x: auto;
|
||||
margin-block-start: calc(var(--typeset-flow) / 0.875);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
&:where(pre code) {
|
||||
background-color: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Blockquote. */
|
||||
&:where(blockquote) {
|
||||
border-inline-start: 2px solid var(--typeset-rule);
|
||||
padding-inline-start: 1em;
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
/* Dividers. */
|
||||
&:where(hr) {
|
||||
border: 0;
|
||||
border-block-start: 1px solid var(--typeset-rule);
|
||||
margin-block-start: calc(var(--typeset-flow) * 2.4);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
&:where(hr + h1, hr + h2, hr + h3, hr + h4) {
|
||||
margin-block-start: var(--typeset-flow);
|
||||
}
|
||||
|
||||
/* GFM footnotes. */
|
||||
&:where(.footnotes, [data-footnotes]) {
|
||||
margin-block-start: calc(var(--typeset-flow) * 2);
|
||||
border-block-start: 1px solid var(--typeset-rule);
|
||||
padding-block-start: var(--typeset-flow);
|
||||
font-size: 0.875em;
|
||||
color: var(--typeset-muted);
|
||||
}
|
||||
|
||||
/* Math. */
|
||||
&:where(math[display="block"]) {
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-block: 0.25em;
|
||||
}
|
||||
|
||||
/* Media. */
|
||||
&:where(img, video) {
|
||||
border-radius: var(--radius, 0.5em);
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
&:where(p img) {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
&:where(figure) {
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
margin-inline: 0;
|
||||
}
|
||||
&:where(figcaption, caption) {
|
||||
color: var(--typeset-muted);
|
||||
font-size: 0.875em;
|
||||
text-align: center;
|
||||
margin-block-start: calc(0.75em / 0.875);
|
||||
}
|
||||
&:where(caption) {
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
/* Tables. Separators sit on cells, not tr:last-child: append-safe.
|
||||
Bare tables stay real tables (keep their semantics) and wrap to fit. */
|
||||
&:where(table) {
|
||||
max-width: 100%;
|
||||
font-size: 1em;
|
||||
line-height: 1.5;
|
||||
font-variant-numeric: tabular-nums;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-block-end: 1px solid var(--typeset-rule);
|
||||
margin-block-start: var(--typeset-flow);
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
/* Horizontal scroll for any wide block. Wrap it and the wrapper owns the
|
||||
flow margin. Tables shrink to fit, so widen the table to make it scroll
|
||||
instead of compress. */
|
||||
&:where(.typeset-scroll) {
|
||||
overflow-x: auto;
|
||||
margin-block-start: var(--typeset-flow);
|
||||
}
|
||||
&:where(.typeset-scroll > *) {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
&:where(.typeset-scroll table) {
|
||||
width: max-content;
|
||||
max-width: none;
|
||||
}
|
||||
&:where(thead th) {
|
||||
font-weight: 500;
|
||||
text-align: start;
|
||||
white-space: nowrap;
|
||||
padding: 0.65em 1em;
|
||||
}
|
||||
&:where(:is(tbody, tfoot) :is(td, th)) {
|
||||
padding: 0.75em 1em;
|
||||
text-align: start;
|
||||
vertical-align: top;
|
||||
border-block-start: 1px solid var(--typeset-rule);
|
||||
}
|
||||
&:where(tbody th, tfoot th) {
|
||||
font-weight: 500;
|
||||
}
|
||||
&:where(th:first-child, td:first-child) {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
&:where(th[align="center"], td[align="center"]) {
|
||||
text-align: center;
|
||||
}
|
||||
&:where(th[align="right"], td[align="right"]) {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
/* Blocks inside list items keep the list's tighter rhythm. */
|
||||
&:where(li > blockquote, li > table, li > figure) {
|
||||
margin-block-start: 0.5em;
|
||||
}
|
||||
&:where(li > pre) {
|
||||
margin-block-start: calc(0.5em / 0.875);
|
||||
}
|
||||
|
||||
@media print {
|
||||
&:where(pre, table, blockquote, figure) {
|
||||
break-inside: avoid;
|
||||
}
|
||||
&:where(h1, h2, h3, h4) {
|
||||
break-after: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
/* Forced colors strips the code background, so give it a border. */
|
||||
@media (forced-colors: active) {
|
||||
&:where(:not(pre) > code) {
|
||||
border: 1px solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* First child adds no space above. Last so it wins ties. */
|
||||
.typeset
|
||||
> :where(:first-child):not(
|
||||
:where(
|
||||
.not-typeset,
|
||||
[data-not-typeset],
|
||||
.not-typeset *,
|
||||
[data-not-typeset] *
|
||||
)
|
||||
),
|
||||
.typeset
|
||||
> :where(:first-child)
|
||||
> :where(:first-child):not(
|
||||
:where(
|
||||
.not-typeset,
|
||||
[data-not-typeset],
|
||||
.not-typeset *,
|
||||
[data-not-typeset] *
|
||||
)
|
||||
),
|
||||
.typeset
|
||||
:where(
|
||||
li > :first-child,
|
||||
blockquote > :first-child,
|
||||
td > :first-child,
|
||||
th > :first-child,
|
||||
dd > :first-child,
|
||||
figure > :first-child,
|
||||
figure > picture > img
|
||||
):not(
|
||||
:where(
|
||||
.not-typeset,
|
||||
[data-not-typeset],
|
||||
.not-typeset *,
|
||||
[data-not-typeset] *
|
||||
)
|
||||
) {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"types": ["react/experimental"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "press.config.tsx", "source.config.ts", ".source"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
Install shadcn/typeset in this Fumapress (Fumadocs + Waku) project.
|
||||
|
||||
Typeset is a single stylesheet that styles rendered markdown: wrap the output
|
||||
in a `typeset` container and everything inside (headings, lists, tables,
|
||||
code, blockquotes, math) is styled. Everything outside is untouched.
|
||||
|
||||
If this project was scaffolded with create-fumapress-typeset, steps 1-4 are
|
||||
already done — verify each one and move on; every step below is safe to
|
||||
re-run.
|
||||
|
||||
1. Download https://ui.shadcn.com/typeset.css and save it as
|
||||
`src/typeset.css` (next to the main CSS file, `src/app.css`). If the
|
||||
file already exists, replace it with the downloaded copy.
|
||||
|
||||
2. Import it in `src/app.css`, after the Tailwind and Fumadocs imports:
|
||||
|
||||
@import "./typeset.css";
|
||||
|
||||
3. Install the fonts:
|
||||
|
||||
bun add @fontsource-variable/dm-sans @fontsource-variable/instrument-sans @fontsource-variable/jetbrains-mono
|
||||
|
||||
Then import them in `src/app.css`:
|
||||
|
||||
@import "@fontsource-variable/dm-sans";
|
||||
@import "@fontsource-variable/instrument-sans";
|
||||
@import "@fontsource-variable/jetbrains-mono";
|
||||
|
||||
:root {
|
||||
--font-dm-sans: "DM Sans Variable", sans-serif;
|
||||
--font-instrument-sans: "Instrument Sans Variable", sans-serif;
|
||||
--font-jetbrains-mono: "JetBrains Mono Variable", monospace;
|
||||
}
|
||||
|
||||
4. Add this preset to `src/app.css`, after the typeset import. If a class
|
||||
named `.typeset-docs` already exists, update its values in place. Leave
|
||||
any other `typeset-*` presets untouched: they are separate surfaces:
|
||||
|
||||
.typeset-docs {
|
||||
--typeset-font-body: var(--font-instrument-sans);
|
||||
--typeset-font-heading: var(--font-dm-sans);
|
||||
--typeset-font-mono: var(--font-jetbrains-mono);
|
||||
--typeset-size: 16px;
|
||||
--typeset-leading: 1.9;
|
||||
--typeset-flow: 1em;
|
||||
}
|
||||
|
||||
5. Do not apply the class anywhere yet. Search the project for surfaces that
|
||||
render markdown or rich content:
|
||||
|
||||
- The Fumadocs docs page body. Fumapress owns that layout: customize it in
|
||||
`press.config.tsx` with `createDocsLayoutPage` from
|
||||
"fumapress/layouts/docs" (see https://press.fumadocs.dev/docs/layouts)
|
||||
and wrap the page body there.
|
||||
- MDX components, react-markdown/Streamdown renderers,
|
||||
dangerouslySetInnerHTML with parsed markdown, `prose` classes, CMS
|
||||
content renderers.
|
||||
|
||||
Present the candidates you find as a short list and ask the user which
|
||||
surface should use typeset. Then wrap only the surface they pick:
|
||||
|
||||
<div className="typeset typeset-docs max-w-[37em]">
|
||||
{content}
|
||||
</div>
|
||||
|
||||
(`src/app.css` also defines `.typeset-measure` with the same
|
||||
max-width, if a plain class is preferred over a Tailwind utility.)
|
||||
|
||||
If the picked surface already has its own typography (Fumadocs' `prose`
|
||||
class, styled markdown components), list those styles and let the user
|
||||
decide what to remove before wrapping. Note: `src/app.css` already zeroes
|
||||
`margin-block-end` inside `.typeset.prose` so typeset owns the rhythm
|
||||
when both classes share a container.
|
||||
|
||||
Notes:
|
||||
|
||||
- To exclude an embedded component from typeset styles, add the
|
||||
`not-typeset` class or the `data-not-typeset` attribute to it. Keep
|
||||
interactive Fumadocs components (tabs, callouts, accordions) out of typeset
|
||||
this way.
|
||||
- Verify on the surface the user picked: headings, lists, tables, and code
|
||||
inside the container should be styled with no classes on the content
|
||||
itself.
|
||||
- Docs: https://ui.shadcn.com/docs/typeset
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from "waku/config";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import press from "fumapress/vite";
|
||||
import mdx from "fumadocs-mdx/vite";
|
||||
|
||||
// Config file for Waku & Vite
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [press(), mdx(), tailwindcss()],
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user