From 71d6b6adb437c50eaf97797a377689521eef6aca Mon Sep 17 00:00:00 2001 From: Fuma Nama <76240755+fuma-nama@users.noreply.github.com> Date: Tue, 14 May 2024 00:06:13 +0800 Subject: [PATCH 01/13] Core: Improve remark-heading plugin --- packages/core/src/mdx-plugins/remark-heading.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/mdx-plugins/remark-heading.ts b/packages/core/src/mdx-plugins/remark-heading.ts index e68d8af0f..0bf5d7df1 100644 --- a/packages/core/src/mdx-plugins/remark-heading.ts +++ b/packages/core/src/mdx-plugins/remark-heading.ts @@ -14,7 +14,7 @@ declare module 'mdast' { } } -const regex = /\s*\[#(?[^]+?)]\s*$/g; +const regex = /\s*\[#(?[^]+?)]\s*$/; export interface RemarkHeadingOptions { slug?: (root: Root, heading: Heading, text: string) => string; From 98258b578c74743cf815533fbe01a99cf2b74ab9 Mon Sep 17 00:00:00 2001 From: Fuma Nama <76240755+fuma-nama@users.noreply.github.com> Date: Tue, 14 May 2024 00:09:38 +0800 Subject: [PATCH 02/13] Add changesets --- .changeset/nine-game-trade.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nine-game-trade.md diff --git a/.changeset/nine-game-trade.md b/.changeset/nine-game-trade.md new file mode 100644 index 000000000..1b1b9ffb7 --- /dev/null +++ b/.changeset/nine-game-trade.md @@ -0,0 +1,5 @@ +--- +"fumadocs-core": patch +--- + +Fix regex problems \ No newline at end of file From e35e9d4940bc077b520f01b8bc9d75512388b067 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 May 2024 16:10:14 +0000 Subject: [PATCH 03/13] Version Packages --- .changeset/nine-game-trade.md | 5 ----- examples/contentlayer/package.json | 6 +++--- examples/next-mdx/package.json | 6 +++--- packages/contentlayer/CHANGELOG.md | 7 +++++++ packages/contentlayer/package.json | 2 +- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/create-app-versions/package.json | 8 ++++---- packages/create-app/CHANGELOG.md | 2 ++ packages/create-app/package.json | 2 +- packages/mdx/CHANGELOG.md | 7 +++++++ packages/mdx/package.json | 2 +- packages/ui/CHANGELOG.md | 7 +++++++ packages/ui/package.json | 2 +- pnpm-lock.yaml | 20 ++++++++++---------- 15 files changed, 54 insertions(+), 30 deletions(-) delete mode 100644 .changeset/nine-game-trade.md diff --git a/.changeset/nine-game-trade.md b/.changeset/nine-game-trade.md deleted file mode 100644 index 1b1b9ffb7..000000000 --- a/.changeset/nine-game-trade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"fumadocs-core": patch ---- - -Fix regex problems \ No newline at end of file diff --git a/examples/contentlayer/package.json b/examples/contentlayer/package.json index 753bf12b0..0bc7220be 100644 --- a/examples/contentlayer/package.json +++ b/examples/contentlayer/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "contentlayer": "0.3.4", - "fumadocs-contentlayer": "1.1.18", - "fumadocs-core": "11.0.7", - "fumadocs-ui": "11.0.7", + "fumadocs-contentlayer": "1.1.19", + "fumadocs-core": "11.0.8", + "fumadocs-ui": "11.0.8", "next": "^14.2.3", "next-contentlayer": "^0.3.4", "react": "18.3.1", diff --git a/examples/next-mdx/package.json b/examples/next-mdx/package.json index 6bd4ea2bc..b192b88e2 100644 --- a/examples/next-mdx/package.json +++ b/examples/next-mdx/package.json @@ -8,9 +8,9 @@ "start": "next start" }, "dependencies": { - "fumadocs-core": "11.0.7", - "fumadocs-mdx": "8.2.16", - "fumadocs-ui": "11.0.7", + "fumadocs-core": "11.0.8", + "fumadocs-mdx": "8.2.17", + "fumadocs-ui": "11.0.8", "next": "^14.2.3", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/packages/contentlayer/CHANGELOG.md b/packages/contentlayer/CHANGELOG.md index c80dc26aa..9ebcad966 100644 --- a/packages/contentlayer/CHANGELOG.md +++ b/packages/contentlayer/CHANGELOG.md @@ -1,5 +1,12 @@ # fumadocs-contentlayer +## 1.1.19 + +### Patch Changes + +- Updated dependencies [98258b5] + - fumadocs-core@11.0.8 + ## 1.1.18 ### Patch Changes diff --git a/packages/contentlayer/package.json b/packages/contentlayer/package.json index aa5e2bc35..e8ee7f884 100644 --- a/packages/contentlayer/package.json +++ b/packages/contentlayer/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-contentlayer", - "version": "1.1.18", + "version": "1.1.19", "description": "The Contentlayer adapter for Fumadocs", "keywords": [ "NextJs", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index cd5848c2a..b85019584 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # next-docs-zeta +## 11.0.8 + +### Patch Changes + +- 98258b5: Fix regex problems + ## 11.0.7 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index b725115c6..1816f37af 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-core", - "version": "11.0.7", + "version": "11.0.8", "description": "The library for building a documentation website in Next.js", "keywords": [ "NextJs", diff --git a/packages/create-app-versions/package.json b/packages/create-app-versions/package.json index ab373c97b..6d3d28333 100644 --- a/packages/create-app-versions/package.json +++ b/packages/create-app-versions/package.json @@ -10,10 +10,10 @@ "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.19", "contentlayer": "^0.3.4", - "fumadocs-contentlayer": "^1.1.18", - "fumadocs-core": "^11.0.7", - "fumadocs-mdx": "^8.2.16", - "fumadocs-ui": "^11.0.7", + "fumadocs-contentlayer": "^1.1.19", + "fumadocs-core": "^11.0.8", + "fumadocs-mdx": "^8.2.17", + "fumadocs-ui": "^11.0.8", "next": "^14.2.3", "next-contentlayer": "^0.3.4", "postcss": "^8.4.38", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index de6ee05ad..62f52a9b3 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,7 @@ # create-next-docs-app +## 11.0.8 + ## 11.0.7 ## 11.0.6 diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 2ff10f975..d0fb2ef7f 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "create-fumadocs-app", - "version": "11.0.7", + "version": "11.0.8", "description": "Create a new documentation site with Fumadocs", "keywords": [ "NextJs", diff --git a/packages/mdx/CHANGELOG.md b/packages/mdx/CHANGELOG.md index 57d2508ca..8e4fe059f 100644 --- a/packages/mdx/CHANGELOG.md +++ b/packages/mdx/CHANGELOG.md @@ -1,5 +1,12 @@ # next-docs-mdx +## 8.2.17 + +### Patch Changes + +- Updated dependencies [98258b5] + - fumadocs-core@11.0.8 + ## 8.2.16 ### Patch Changes diff --git a/packages/mdx/package.json b/packages/mdx/package.json index d41dae18c..61afe226b 100644 --- a/packages/mdx/package.json +++ b/packages/mdx/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-mdx", - "version": "8.2.16", + "version": "8.2.17", "description": "The built-in source for Fumadocs", "keywords": [ "NextJs", diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 75349b956..f83e6da79 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,12 @@ # next-docs-ui +## 11.0.8 + +### Patch Changes + +- Updated dependencies [98258b5] + - fumadocs-core@11.0.8 + ## 11.0.7 ### Patch Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index a1d75fa9e..e217646e5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "fumadocs-ui", - "version": "11.0.7", + "version": "11.0.8", "description": "The framework for building a documentation website in Next.js", "keywords": [ "NextJs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50d09a8b1..ad8552396 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,13 +183,13 @@ importers: specifier: 0.3.4 version: 0.3.4 fumadocs-contentlayer: - specifier: 1.1.18 + specifier: 1.1.19 version: link:../../packages/contentlayer fumadocs-core: - specifier: 11.0.7 + specifier: 11.0.8 version: link:../../packages/core fumadocs-ui: - specifier: 11.0.7 + specifier: 11.0.8 version: link:../../packages/ui next: specifier: ^14.2.3 @@ -226,13 +226,13 @@ importers: examples/next-mdx: dependencies: fumadocs-core: - specifier: 11.0.7 + specifier: 11.0.8 version: link:../../packages/core fumadocs-mdx: - specifier: 8.2.16 + specifier: 8.2.17 version: link:../../packages/mdx fumadocs-ui: - specifier: 11.0.7 + specifier: 11.0.8 version: link:../../packages/ui next: specifier: ^14.2.3 @@ -428,16 +428,16 @@ importers: specifier: ^0.3.4 version: 0.3.4 fumadocs-contentlayer: - specifier: ^1.1.18 + specifier: ^1.1.19 version: link:../contentlayer fumadocs-core: - specifier: ^11.0.7 + specifier: ^11.0.8 version: link:../core fumadocs-mdx: - specifier: ^8.2.16 + specifier: ^8.2.17 version: link:../mdx fumadocs-ui: - specifier: ^11.0.7 + specifier: ^11.0.8 version: link:../ui next: specifier: ^14.2.3 From 210b41197bfcc6f7b1bceb75ccd43ce7e0f337e7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 12:41:06 +0000 Subject: [PATCH 04/13] Update all non-major dependencies to v1.5.2 --- packages/twoslash/package.json | 4 ++-- pnpm-lock.yaml | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/twoslash/package.json b/packages/twoslash/package.json index c12c128f0..60f8f9f7b 100644 --- a/packages/twoslash/package.json +++ b/packages/twoslash/package.json @@ -25,11 +25,11 @@ "types:check": "tsc --noEmit" }, "dependencies": { - "@shikijs/twoslash": "1.5.1", + "@shikijs/twoslash": "1.5.2", "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm": "^3.0.0", "mdast-util-to-hast": "^13.1.0", - "shiki": "1.5.1" + "shiki": "1.5.2" }, "devDependencies": { "@types/hast": "^3.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad8552396..bf909ed98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -597,8 +597,8 @@ importers: packages/twoslash: dependencies: '@shikijs/twoslash': - specifier: 1.5.1 - version: 1.5.1(typescript@5.4.5) + specifier: 1.5.2 + version: 1.5.2(typescript@5.4.5) mdast-util-from-markdown: specifier: ^2.0.0 version: 2.0.0 @@ -609,8 +609,8 @@ importers: specifier: ^13.1.0 version: 13.1.0 shiki: - specifier: 1.5.1 - version: 1.5.1 + specifier: 1.5.2 + version: 1.5.2 devDependencies: '@types/hast': specifier: ^3.0.4 @@ -3630,8 +3630,8 @@ packages: resolution: {integrity: sha512-7fedsBfuILDTBmrYZNFI8B6ATTxhQAasUHllHmjvSZPnoq4bULWoTpHwmuQvZ8Aq03/tAa2IGo6RXqWtHdWaCA==} dev: false - /@shikijs/core@1.5.1: - resolution: {integrity: sha512-xjV63pRUBvxA1LsxOUhRKLPh0uUjwBLzAKLdEuYSLIylo71sYuwDcttqNP01Ib1TZlLfO840CXHPlgUUsYFjzg==} + /@shikijs/core@1.5.2: + resolution: {integrity: sha512-wSAOgaz48GmhILFElMCeQypSZmj6Ru6DttOOtl3KNkdJ17ApQuGNCfzpk4cClasVrnIu45++2DBwG4LNMQAfaA==} dev: false /@shikijs/rehype@1.3.0: @@ -3651,10 +3651,10 @@ packages: shiki: 1.3.0 dev: false - /@shikijs/twoslash@1.5.1(typescript@5.4.5): - resolution: {integrity: sha512-O0cnGcpW1LkBLd85TQp7Kdb9qzhSGyYl9c21BCAmYWhQdtnxaSKBgbiP3S35ewP/s3SrR9gCzumgznp/YSyMNg==} + /@shikijs/twoslash@1.5.2(typescript@5.4.5): + resolution: {integrity: sha512-elXSplqPxoifKAzCO9TJ6zkNN9XjM22+zU8OD+0tH6KDgk/8qaLQ48rCTg8/Ir7nvk0VuGnFz4cc5eQ5oX009A==} dependencies: - '@shikijs/core': 1.5.1 + '@shikijs/core': 1.5.2 twoslash: 0.2.6(typescript@5.4.5) transitivePeerDependencies: - supports-color @@ -9943,10 +9943,10 @@ packages: '@shikijs/core': 1.3.0 dev: false - /shiki@1.5.1: - resolution: {integrity: sha512-vx4Ds3M3B9ZEmLeSXqBAB85osBWV8ErZfP69kuFQZozPgHc33m7spLTCUkcjwEjFm3gk3F9IdXMv8kX+v9xDHA==} + /shiki@1.5.2: + resolution: {integrity: sha512-fpPbuSaatinmdGijE7VYUD3hxLozR3ZZ+iAx8Iy2X6REmJGyF5hQl94SgmiUNTospq346nXUVZx0035dyGvIVw==} dependencies: - '@shikijs/core': 1.5.1 + '@shikijs/core': 1.5.2 dev: false /side-channel@1.0.4: From 7d52749691fca20ff8c5909391544d3e303e183c Mon Sep 17 00:00:00 2001 From: fuma-nama Date: Tue, 14 May 2024 23:06:28 +0800 Subject: [PATCH 05/13] Experimental: Support menu link items --- apps/docs/app/layout.config.tsx | 19 ++- packages/core/test/fixtures/remark-heading.md | 4 + .../test/fixtures/remark-heading.output.json | 5 + packages/ui/src/components/nav.tsx | 150 +++++++++++------- packages/ui/src/components/sidebar.tsx | 143 ++++++++++------- packages/ui/src/components/ui/popover.tsx | 2 +- packages/ui/src/layout.tsx | 30 ++-- 7 files changed, 232 insertions(+), 121 deletions(-) diff --git a/apps/docs/app/layout.config.tsx b/apps/docs/app/layout.config.tsx index 5614c1a5d..177872852 100644 --- a/apps/docs/app/layout.config.tsx +++ b/apps/docs/app/layout.config.tsx @@ -1,5 +1,5 @@ import type { DocsLayoutProps } from 'fumadocs-ui/layout'; -import { LayoutTemplateIcon } from 'lucide-react'; +import { BookIcon, LayoutTemplateIcon } from 'lucide-react'; import { utils } from '@/utils/source'; import { NavChildren, SidebarBanner, Title } from '@/app/layout.client'; @@ -21,5 +21,22 @@ export const layoutOptions: Omit = { url: '/showcase', icon: , }, + { + type: 'menu', + text: 'Test', + icon: , + items: [ + { + text: 'Getting Started', + url: '/docs/ui', + icon: , + }, + { + text: 'Learn More', + url: '/docs', + icon: , + }, + ], + }, ], }; diff --git a/packages/core/test/fixtures/remark-heading.md b/packages/core/test/fixtures/remark-heading.md index 7d14804da..8fb4ecaec 100644 --- a/packages/core/test/fixtures/remark-heading.md +++ b/packages/core/test/fixtures/remark-heading.md @@ -14,6 +14,10 @@ Some text here Some text here +### Custom heading id [#hello-2] + +Some text here + ### Custom heading id hello-world] Some text here diff --git a/packages/core/test/fixtures/remark-heading.output.json b/packages/core/test/fixtures/remark-heading.output.json index c277dc2fc..872b4b2cc 100644 --- a/packages/core/test/fixtures/remark-heading.output.json +++ b/packages/core/test/fixtures/remark-heading.output.json @@ -19,6 +19,11 @@ "title": "Custom heading id", "url": "#hello-world", }, + { + "depth": 3, + "title": "Custom heading id", + "url": "#hello-2", + }, { "depth": 3, "title": "Custom heading id hello-world]", diff --git a/packages/ui/src/components/nav.tsx b/packages/ui/src/components/nav.tsx index c740bbe40..02e36d69e 100644 --- a/packages/ui/src/components/nav.tsx +++ b/packages/ui/src/components/nav.tsx @@ -1,5 +1,10 @@ 'use client'; -import { MenuIcon, MoreVerticalIcon, SearchIcon } from 'lucide-react'; +import { + ChevronDownIcon, + MenuIcon, + MoreVerticalIcon, + SearchIcon, +} from 'lucide-react'; import Link from 'fumadocs-core/link'; import { SidebarTrigger } from 'fumadocs-core/sidebar'; import { usePathname } from 'next/navigation'; @@ -21,6 +26,7 @@ import { import { isActive } from '@/utils/shared'; import { buttonVariants } from '@/theme/variants'; import type { LinkItem } from '@/layout'; +import { cva } from 'class-variance-authority'; export interface NavProps { title?: ReactNode; @@ -85,7 +91,7 @@ export function Nav({ : 'border-foreground/10 bg-background/50 backdrop-blur-md', )} > - @@ -154,11 +142,16 @@ export function Nav({ } interface LinksMenuProps extends React.ButtonHTMLAttributes { - items: LinkItem[]; + items: LinkItemType[]; } function LinksMenu({ items, ...props }: LinksMenuProps): React.ReactElement { const [open, setOpen] = useState(false); + const pathname = usePathname(); + + useEffect(() => { + setOpen(false); + }, [pathname]); return ( @@ -174,16 +167,9 @@ function LinksMenu({ items, ...props }: LinksMenuProps): React.ReactElement { > - + {items.map((item, i) => ( - { - setOpen(false); - }} - /> + ))} @@ -194,7 +180,7 @@ function LinksMenu({ items, ...props }: LinksMenuProps): React.ReactElement { function SearchToggle(): React.ReactElement { const { setOpenSearch } = useSearchContext(); const { text } = useI18n(); - const onClick = () => { + const onClick = (): void => { setOpenSearch(true); }; @@ -232,63 +218,3 @@ function SearchToggle(): React.ReactElement { ); } - -const linkItemVariants = cva( - 'inline-flex items-center gap-1.5 p-2 rounded-lg text-muted-foreground text-base transition-colors data-[state=open]:bg-accent [&_svg]:size-4', - { - variants: { - active: { - true: 'text-accent-foreground bg-accent', - false: 'hover:bg-accent', - }, - }, - }, -); - -function NavItem({ - item, - showIcon = false, - className, - ...props -}: AnchorHTMLAttributes & { - item: LinkItem; - showIcon?: boolean; -}): React.ReactElement { - const pathname = usePathname(); - - if (item.type === 'menu') { - return ( - - - {showIcon ? item.icon : null} - {item.text} - - - - {item.items.map((child, i) => ( - - ))} - - - ); - } - - return ( - - {showIcon ? item.icon : null} - {item.text} - - ); -} diff --git a/packages/ui/src/components/sidebar.tsx b/packages/ui/src/components/sidebar.tsx index 5fc6ce342..738e9e6f7 100644 --- a/packages/ui/src/components/sidebar.tsx +++ b/packages/ui/src/components/sidebar.tsx @@ -11,8 +11,9 @@ import { useTreeContext } from '@/contexts/tree'; import { useSidebarCollapse } from '@/contexts/sidebar'; import { ScrollArea, ScrollViewport } from '@/components/ui/scroll-area'; import { hasActive, isActive } from '@/utils/shared'; -import type { LinkItem } from '@/layout'; +import type { LinkItemType } from '@/layout'; import { buttonVariants } from '@/theme/variants'; +import { LinkItem } from '@/components/link-item'; import { Collapsible, CollapsibleContent, @@ -21,7 +22,7 @@ import { import { ThemeToggle } from './theme-toggle'; export interface SidebarProps { - items: LinkItem[]; + items: LinkItemType[]; /** * Open folders by default if their level is lower or equal to a specific level @@ -65,7 +66,7 @@ const itemVariants = cva( const defaultComponents: Components = { Folder: FolderNode, Separator: SeparatorNode, - Item: ({ item }) => , + Item: PageNode, }; const SidebarContext = createContext({ @@ -163,9 +164,9 @@ function ViewportContent({
{banner} {items.length > 0 && ( -
+
{items.map((item, i) => ( - + ))}
)} @@ -206,13 +207,13 @@ function NodeList({ ); } -function BaseItem({ - icon, - external = false, - url, - name, +function PageNode({ + item: { icon, external = false, url, name }, nested = false, -}: Omit & { nested?: boolean }): React.ReactElement { +}: { + item: PageTree.Item; + nested?: boolean; +}): React.ReactElement { const pathname = usePathname(); const active = isActive(url, pathname, nested); @@ -300,42 +301,6 @@ function FolderNode({ ); } -function LinkItemRender({ item }: { item: LinkItem }) { - if (item.type === 'menu') { - return ( - - - {item.icon} - {item.text} - - - -
- {item.items.map((child, i) => ( - - ))} -
-
-
- ); - } - return ( - - ); -} - function SeparatorNode({ item, }: { diff --git a/packages/ui/src/layout.tsx b/packages/ui/src/layout.tsx index 0fcf93f61..64abfdc70 100644 --- a/packages/ui/src/layout.tsx +++ b/packages/ui/src/layout.tsx @@ -11,10 +11,18 @@ declare const { Sidebar, }: typeof import('./layout.client'); -export type LinkItem = +type ActiveType = 'none' | 'url' | 'nested-url'; + +export type LinkItemType = | { type?: 'main'; url: string; + /** + * When the item is marked as active + * + * @defaultValue 'url' + */ + active?: ActiveType; icon?: ReactNode; text: string; external?: boolean; @@ -23,11 +31,17 @@ export type LinkItem = type: 'menu'; icon?: ReactNode; text: string; - items: LinkItem[]; + items: LinkItemType[]; } | { type: 'secondary'; url: string; + /** + * When the item is marked as active + * + * @defaultValue 'url' + */ + active?: ActiveType; icon: ReactNode; text: string; external?: boolean; @@ -51,7 +65,7 @@ interface SidebarOptions extends Omit { } export interface BaseLayoutProps { - links?: LinkItem[]; + links?: LinkItemType[]; /** * Replace or disable navbar */ @@ -132,7 +146,7 @@ export function DocsLayout({ ); } -function getLinks(links?: LinkItem[], githubUrl?: string): LinkItem[] { +function getLinks(links?: LinkItemType[], githubUrl?: string): LinkItemType[] { let result = links ?? []; if (githubUrl) From 02a014fd73be1c16d58af1b745f2e4fc11af7a39 Mon Sep 17 00:00:00 2001 From: fuma-nama Date: Wed, 15 May 2024 16:46:37 +0800 Subject: [PATCH 07/13] Improve link item renderer --- .changeset/long-trees-pull.md | 5 ++ apps/docs/app/layout.config.tsx | 4 +- packages/ui/src/components/link-item.tsx | 69 +++++++++++++++++++++--- packages/ui/src/components/nav.tsx | 28 +++------- packages/ui/src/components/sidebar.tsx | 30 ++++++----- 5 files changed, 90 insertions(+), 46 deletions(-) create mode 100644 .changeset/long-trees-pull.md diff --git a/.changeset/long-trees-pull.md b/.changeset/long-trees-pull.md new file mode 100644 index 000000000..a627b5c9e --- /dev/null +++ b/.changeset/long-trees-pull.md @@ -0,0 +1,5 @@ +--- +"fumadocs-ui": minor +--- + +Support custom menu items in navbar \ No newline at end of file diff --git a/apps/docs/app/layout.config.tsx b/apps/docs/app/layout.config.tsx index 177872852..9eefac5ec 100644 --- a/apps/docs/app/layout.config.tsx +++ b/apps/docs/app/layout.config.tsx @@ -23,18 +23,16 @@ export const layoutOptions: Omit = { }, { type: 'menu', - text: 'Test', + text: 'Guide', icon: , items: [ { text: 'Getting Started', url: '/docs/ui', - icon: , }, { text: 'Learn More', url: '/docs', - icon: , }, ], }, diff --git a/packages/ui/src/components/link-item.tsx b/packages/ui/src/components/link-item.tsx index 5dc293941..7d7d2cf52 100644 --- a/packages/ui/src/components/link-item.tsx +++ b/packages/ui/src/components/link-item.tsx @@ -10,9 +10,15 @@ import { } from '@/components/ui/popover'; import { cn } from '@/utils/cn'; import { isActive } from '@/utils/shared'; +import { buttonVariants } from '@/theme/variants'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible'; const linkItemVariants = cva( - 'inline-flex items-center gap-1.5 rounded-lg p-2 text-base text-muted-foreground transition-colors data-[state=open]:bg-accent [&_svg]:size-4', + 'inline-flex items-center gap-1.5 rounded-lg p-2 text-[15px] text-muted-foreground transition-colors [&_svg]:size-4', { variants: { active: { @@ -20,48 +26,95 @@ const linkItemVariants = cva( false: 'hover:bg-accent', }, }, + defaultVariants: { + active: false, + }, }, ); interface LinkItemProps extends React.HTMLAttributes { item: LinkItemType; - showIcon?: boolean; + on?: 'menu' | 'nav'; } export function LinkItem({ item, - showIcon = false, + on = 'nav', className, ...props }: LinkItemProps): React.ReactElement { const pathname = usePathname(); - if (item.type === 'menu') { + if (item.type === 'menu' && on === 'nav') { return ( - {showIcon ? item.icon : null} {item.text} {item.items.map((child, i) => ( - + ))} ); } + if (item.type === 'menu') { + return ( + + + {item.icon} + {item.text} + + + +
+ {item.items.map((child, i) => ( + + ))} +
+
+
+ ); + } + const activeType = item.active ?? 'url'; const active = activeType !== 'none' ? isActive(item.url, pathname, activeType === 'nested-url') : false; + if (item.type === 'secondary' && on === 'nav') { + return ( + + {item.icon} + + ); + } + return ( - {showIcon ? item.icon : null} + {on === 'menu' ? item.icon : null} {item.text} ); diff --git a/packages/ui/src/components/nav.tsx b/packages/ui/src/components/nav.tsx index b5d37c51b..458a3447c 100644 --- a/packages/ui/src/components/nav.tsx +++ b/packages/ui/src/components/nav.tsx @@ -114,27 +114,11 @@ export function Nav({ items={items} className={cn('lg:hidden', enableSidebar && 'max-md:hidden')} /> - {items.flatMap((item, i) => - item.type === 'secondary' ? ( - - {item.icon ?? item.text} - - ) : ( - [] - ), - )} + {items + .filter((item) => item.type === 'secondary') + .map((item, i) => ( + + ))}
@@ -169,7 +153,7 @@ function LinksMenu({ items, ...props }: LinksMenuProps): React.ReactElement { {items.map((item, i) => ( - + ))} diff --git a/packages/ui/src/components/sidebar.tsx b/packages/ui/src/components/sidebar.tsx index 738e9e6f7..40af91b4e 100644 --- a/packages/ui/src/components/sidebar.tsx +++ b/packages/ui/src/components/sidebar.tsx @@ -79,7 +79,8 @@ export function Sidebar({ components, defaultOpenLevel = 1, collapsible = true, - ...props + banner, + items, }: SidebarProps): React.ReactElement { const [open, setOpen] = useSidebarCollapse(); const alwaysShowFooter = Boolean(footer) || collapsible; @@ -121,7 +122,16 @@ export function Sidebar({ 'max-md:fixed max-md:inset-0 max-md:z-40 max-md:bg-background/80 max-md:pt-16 max-md:backdrop-blur-md max-md:data-[open=false]:hidden', )} > - + + {banner} + {items.length > 0 && ( +
+ {items.map((item, i) => ( + + ))} +
+ )} +
): React.ReactElement { + children, +}: { + children: React.ReactNode; +}): React.ReactElement { const { root } = useTreeContext(); return (
- {banner} - {items.length > 0 && ( -
- {items.map((item, i) => ( - - ))} -
- )} + {children}
From 22d960df7d3a5846fa84211c9ac3837d1a48e0ac Mon Sep 17 00:00:00 2001 From: fuma-nama Date: Wed, 15 May 2024 17:16:11 +0800 Subject: [PATCH 08/13] Format code --- packages/ui/.eslintrc.cjs | 2 +- packages/ui/src/components/link-item.tsx | 6 +++--- packages/ui/src/components/ui/popover.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/.eslintrc.cjs b/packages/ui/.eslintrc.cjs index c784db0df..b350c06b1 100644 --- a/packages/ui/.eslintrc.cjs +++ b/packages/ui/.eslintrc.cjs @@ -4,6 +4,6 @@ module.exports = { // for the import hacks '@typescript-eslint/consistent-type-imports': 'off', // some arrays like link items won't be changed - "react/no-array-index-key": "off" + 'react/no-array-index-key': 'off', }, }; diff --git a/packages/ui/src/components/link-item.tsx b/packages/ui/src/components/link-item.tsx index 7d7d2cf52..ec19f953c 100644 --- a/packages/ui/src/components/link-item.tsx +++ b/packages/ui/src/components/link-item.tsx @@ -71,15 +71,15 @@ export function LinkItem({ return ( {item.icon} {item.text} - + -
+
{item.items.map((child, i) => ( ))} diff --git a/packages/ui/src/components/ui/popover.tsx b/packages/ui/src/components/ui/popover.tsx index 0dc88645c..58ffcc40f 100644 --- a/packages/ui/src/components/ui/popover.tsx +++ b/packages/ui/src/components/ui/popover.tsx @@ -17,7 +17,7 @@ const PopoverContent = React.forwardRef< sideOffset={sideOffset} side="bottom" className={cn( - 'z-50 min-w-[260px] max-w-[90vw] rounded-md border text-sm bg-popover p-2 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-popover-out data-[state=open]:animate-popover-in', + 'z-50 min-w-[260px] max-w-[90vw] rounded-md border bg-popover p-2 text-sm text-popover-foreground shadow-md outline-none data-[state=closed]:animate-popover-out data-[state=open]:animate-popover-in', className, )} {...props} From 76d3bbcc5bd8adf4f5bd9507b69e6271770260bc Mon Sep 17 00:00:00 2001 From: fuma-nama Date: Wed, 15 May 2024 18:48:10 +0800 Subject: [PATCH 09/13] Build fumadocs blog --- .../app/(home)/blog/[slug]/page.client.tsx | 24 ++++ apps/docs/app/(home)/blog/[slug]/page.tsx | 76 +++++++++++++ apps/docs/app/(home)/blog/page.tsx | 57 ++++++++++ apps/docs/app/layout.config.tsx | 21 +--- apps/docs/content/blog/2024-5-15.mdx | 106 ++++++++++++++++++ apps/docs/utils/source.ts | 29 +++-- 6 files changed, 291 insertions(+), 22 deletions(-) create mode 100644 apps/docs/app/(home)/blog/[slug]/page.client.tsx create mode 100644 apps/docs/app/(home)/blog/[slug]/page.tsx create mode 100644 apps/docs/app/(home)/blog/page.tsx create mode 100644 apps/docs/content/blog/2024-5-15.mdx diff --git a/apps/docs/app/(home)/blog/[slug]/page.client.tsx b/apps/docs/app/(home)/blog/[slug]/page.client.tsx new file mode 100644 index 000000000..b5f3922ab --- /dev/null +++ b/apps/docs/app/(home)/blog/[slug]/page.client.tsx @@ -0,0 +1,24 @@ +'use client'; +import { cn } from '@/utils/cn'; +import { buttonVariants } from '@/components/ui/button'; +import { ShareIcon } from 'lucide-react'; + +export function Control({ url }: { url: string }) { + const onClick = () => { + void navigator.clipboard.writeText(`${window.location.origin}${url}`); + }; + + return ( + <> + + + ); +} diff --git a/apps/docs/app/(home)/blog/[slug]/page.tsx b/apps/docs/app/(home)/blog/[slug]/page.tsx new file mode 100644 index 000000000..1b672eaf2 --- /dev/null +++ b/apps/docs/app/(home)/blog/[slug]/page.tsx @@ -0,0 +1,76 @@ +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import { blog } from '@/utils/source'; +import { createMetadata } from '@/utils/metadata'; +import Link from 'next/link'; +import { buttonVariants } from '@/components/ui/button'; +import { cn } from '@/utils/cn'; +import { ShareIcon } from 'lucide-react'; +import { Control } from '@/app/(home)/blog/[slug]/page.client'; + +interface Param { + slug: string; +} + +export const dynamicParams = false; + +export default function Page({ + params, +}: { + params: Param; +}): React.ReactElement { + const page = blog.getPage([params.slug]); + + if (!page) notFound(); + + return ( + <> +
+

{page.data.title}

+

{page.data.description}

+ + Back + +
+
+
+ +
+
+
+

Written by

+

{page.data.author}

+
+
+

At

+

+ {new Date(page.data.date ?? page.file.name).toDateString()} +

+
+ +
+
+ + ); +} + +export function generateMetadata({ params }: { params: Param }): Metadata { + const page = blog.getPage([params.slug]); + + if (!page) notFound(); + + return createMetadata({ + title: page.data.title, + description: + page.data.description ?? 'The library for building documentation sites', + }); +} + +export function generateStaticParams(): Param[] { + return blog.getPages().map((page) => ({ + slug: page.slugs[0], + })); +} diff --git a/apps/docs/app/(home)/blog/page.tsx b/apps/docs/app/(home)/blog/page.tsx new file mode 100644 index 000000000..1a3fe5692 --- /dev/null +++ b/apps/docs/app/(home)/blog/page.tsx @@ -0,0 +1,57 @@ +import { blog } from '@/utils/source'; +import Link from 'next/link'; + +export default function Page() { + const posts = blog.getPages(); + + const svg = ` + + + + + +`; + + return ( +
+
+

+ Fumadocs Blog +

+

Light and gorgeous. like the moon

+
+
+ {posts.map((post) => ( + +

{post.data.title}

+

+ {post.data.description} +

+ +

+ {new Date(post.data.date ?? post.file.name).toDateString()} +

+ + ))} +
+
+ ); +} diff --git a/apps/docs/app/layout.config.tsx b/apps/docs/app/layout.config.tsx index 9eefac5ec..64d676705 100644 --- a/apps/docs/app/layout.config.tsx +++ b/apps/docs/app/layout.config.tsx @@ -16,25 +16,16 @@ export const layoutOptions: Omit = { banner: , }, links: [ + { + icon: , + text: 'Blog', + url: '/blog', + active: 'nested-url', + }, { text: 'Showcase', url: '/showcase', icon: , }, - { - type: 'menu', - text: 'Guide', - icon: , - items: [ - { - text: 'Getting Started', - url: '/docs/ui', - }, - { - text: 'Learn More', - url: '/docs', - }, - ], - }, ], }; diff --git a/apps/docs/content/blog/2024-5-15.mdx b/apps/docs/content/blog/2024-5-15.mdx new file mode 100644 index 000000000..55712a986 --- /dev/null +++ b/apps/docs/content/blog/2024-5-15.mdx @@ -0,0 +1,106 @@ +--- +title: How Fumadocs works +description: The framework for building documentation +author: Fuma Nama +--- + +## About Fumadocs + +1 year ago, I was having fun with Next.js App Rouer. +While experimenting it on my toy [No Deploy](https://nodeploy-neon.vercel.app), I planned to build a documentation. +However, Nextra does not support App Router. + +To handle this, I implemented a small documentation site with solely Contentlayer and the new features from App Router. +It was working great, looks blazing-fast and minimal. +I cloned the logic from No Deploy and built this documentation framework. +With a few months of development, it soon became powerful and stable. + +It was originally named `next-docs`, I renamed it to Fumadocs as it conflicts with Next.js Docs. + +Thanks to the support from **Next.js** community, I've received a lot of suggestions along the way. +[Fumadocs](https://fumadocs.vercel.app) is now a framework used by my libraries and some other amazing projects. + +### My Opinion + +In Web development, most _"robust"_ frameworks/libraries are incredibly heavy and fabulous, but it indeed made our developer experience fancy. + +On the top of Javascript, people bulit bundlers, transpilers, and even Typescript. +It feels very surprisingly that Javascript, a high-level scripting language, is more similar to assembly code in modern Web development. +We rarely use them without things like Webpack. This also applies to CSS, at least as my experience, I seldom use CSS without PostCSS. + +While they might be necessary for compatibility and DX, the landing of React Server Component and Next.js App Router made the experience even more mindblowing. +It feels like magic. The cunning magical frameworks, and web development miracles. +This kind of design is insane, but it also makes us mindlessly forget the boundaries. + +Beginners use Metadata API, while they have no idea how meta tag works. +They put server-side logic in a server component, while they have no idea how expensive the calculation is. +Even when we looked at the code, we can't predict the result without running it in production mode. +I saw too many of these misconceptions. + +This happens on many frameworks, they are overly magical. +**I wanted to make it less-magic, and straightforward at least for most Next.js developers.** + +### Fumadocs MDX + +As the recommended content source, It is actually a webpack hack. +Since Next.js could only optimize static imports, it first transform your `.map.ts` to a file that roughy yields: + +```ts +export default [import("./my/file.mdx"), ...]; +``` + +And then transform MDX files with a custom loader. It makes all magic possible, but it doesn't have the ablility of lazy-loading MDX files. +Comparing to Nextra, it might be a suboptimal approach. + +Nextra does it even easier, it directly transforms MDX files into pages. Because the Pages Router adapts Javascript files as a single page, it is possible. +In App Router, this is not possible anymore. Therefore, I didn't take this approach. + +### Fumadocs Core + +The core of Fumadocs is a bunch of utilties and MDX plugins. + +- **Source API** construct page trees from content source, integrated with other content providers. +- **Headless components** accelerate Fumadocs UI and other custom UI implementations. +- **MDX plugins** bring a perfect developer experience to all integrations. +- **Search utilities** make it way easier to implement document search. + +In addition, it also established the definitions of Page Tree and Page Conventions. +Over all, it is not yet a framework without Fumadocs UI. + +In my opinion, the most valuable part in the codebase are MDX plugins. +I learnt a lot more about ASTs and the eco-system of remark/rehype while working on them. +Absolutely an amazing experience. + +### Fumadocs UI + +The UI implementation of Fumadocs using Tailwind CSS and Radix UI. +Its design system was inspired by Shadcn UI, using CSS variables for color utilities. + +Although the structure of Fumadocs UI is even simpler than core, I've used some subtle hacks to solve the problem of `"use client"` directive. +The bundler I am using, [TSX](https://github.com/privatenumber/tsx), can't handle nested structures like client components imported from a server comopnent. +Therefore, I made a little hack to build server components and client components as an individual entry, then inject import statements after the process. + +Also it took me some time to come up with the [preset approach](https://fumadocs.vercel.app/docs/ui/theme#docsui-plugin) for integrating Fumadocs UI with Tailwind CSS projects. + +### Docs Generators + +We have a few built-in integrations, like `fumadocs-openapi` which takes an OpenAPI schema and output MDX files. + +For the OpenAPI one, it simply parse the schema and convert it to MDX file through string templates. + +The Typescript integration does a bit more, it obtain type information with [Typscript Compiler API](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API). Based on the information, it yields MDX files. +You can use it inside a server component, which is how `` works. + +### CL/CI + +As a project with very few contributors, I built the CL/CI process as convenient as possible for a better efficiency. +The entire release process is handled by [Changesets](https://github.com/changesets/changesets), and I wrote the scripts to update [the template repository](https://github.com/fuma-nama/fumadocs-ui-template) automatically. +It worked great so far. + +### Thanks + +[The Github repository of Fumadocs](https://github.com/fuma-nama/fumadocs) has reached 300 stars in 2024 March, it is a new achievement for me. +Welcome to give it a star to support my work! + +> Original +> https://fuma-nama.vercel.app/blog/fumadocs \ No newline at end of file diff --git a/apps/docs/utils/source.ts b/apps/docs/utils/source.ts index 59f50ae84..6dda94553 100644 --- a/apps/docs/utils/source.ts +++ b/apps/docs/utils/source.ts @@ -6,12 +6,6 @@ import { icons } from 'lucide-react'; import { map } from '@/.map'; import { create } from '@/components/ui/icon'; -const frontmatterSchema = defaultSchemas.frontmatter.extend({ - preview: z.string().optional(), - toc: z.boolean().default(true), - index: z.boolean().default(false), -}); - export const utils = loader({ baseUrl: '/docs', rootDir: 'docs', @@ -19,7 +13,28 @@ export const utils = loader({ if (icon in icons) return create({ icon: icons[icon as keyof typeof icons] }); }, - source: createMDXSource(map, { schema: { frontmatter: frontmatterSchema } }), + source: createMDXSource(map, { + schema: { + frontmatter: defaultSchemas.frontmatter.extend({ + preview: z.string().optional(), + toc: z.boolean().default(true), + index: z.boolean().default(false), + }), + }, + }), +}); + +export const blog = loader({ + baseUrl: '/blog', + rootDir: 'blog', + source: createMDXSource(map, { + schema: { + frontmatter: defaultSchemas.frontmatter.extend({ + author: z.string(), + date: z.string().date().optional(), + }), + }, + }), }); export type Page = InferPageType; From 8143c8643d9a2c7f49819582f329189868c29592 Mon Sep 17 00:00:00 2001 From: fuma-nama Date: Wed, 15 May 2024 18:53:23 +0800 Subject: [PATCH 10/13] Fix lint warnings --- .../app/(home)/blog/[slug]/page.client.tsx | 26 +++++++++---------- apps/docs/app/(home)/blog/[slug]/page.tsx | 16 +++++------- apps/docs/app/(home)/blog/page.tsx | 12 ++++----- apps/docs/utils/metadata.ts | 2 +- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/apps/docs/app/(home)/blog/[slug]/page.client.tsx b/apps/docs/app/(home)/blog/[slug]/page.client.tsx index b5f3922ab..b8dfe9856 100644 --- a/apps/docs/app/(home)/blog/[slug]/page.client.tsx +++ b/apps/docs/app/(home)/blog/[slug]/page.client.tsx @@ -1,24 +1,22 @@ 'use client'; +import { ShareIcon } from 'lucide-react'; import { cn } from '@/utils/cn'; import { buttonVariants } from '@/components/ui/button'; -import { ShareIcon } from 'lucide-react'; -export function Control({ url }: { url: string }) { - const onClick = () => { +export function Control({ url }: { url: string }): React.ReactElement { + const onClick = (): void => { void navigator.clipboard.writeText(`${window.location.origin}${url}`); }; return ( - <> - - + ); } diff --git a/apps/docs/app/(home)/blog/[slug]/page.tsx b/apps/docs/app/(home)/blog/[slug]/page.tsx index 1b672eaf2..7cfe624e2 100644 --- a/apps/docs/app/(home)/blog/[slug]/page.tsx +++ b/apps/docs/app/(home)/blog/[slug]/page.tsx @@ -1,11 +1,9 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; +import Link from 'next/link'; import { blog } from '@/utils/source'; import { createMetadata } from '@/utils/metadata'; -import Link from 'next/link'; import { buttonVariants } from '@/components/ui/button'; -import { cn } from '@/utils/cn'; -import { ShareIcon } from 'lucide-react'; import { Control } from '@/app/(home)/blog/[slug]/page.client'; interface Param { @@ -25,9 +23,9 @@ export default function Page({ return ( <> -
-

{page.data.title}

-

{page.data.description}

+
+

{page.data.title}

+

{page.data.description}

-
+
-

Written by

+

Written by

{page.data.author}

-

At

+

At

{new Date(page.data.date ?? page.file.name).toDateString()}

diff --git a/apps/docs/app/(home)/blog/page.tsx b/apps/docs/app/(home)/blog/page.tsx index 1a3fe5692..96e84dfa9 100644 --- a/apps/docs/app/(home)/blog/page.tsx +++ b/apps/docs/app/(home)/blog/page.tsx @@ -1,7 +1,7 @@ -import { blog } from '@/utils/source'; import Link from 'next/link'; +import { blog } from '@/utils/source'; -export default function Page() { +export default function Page(): React.ReactElement { const posts = blog.getPages(); const svg = ` @@ -19,7 +19,7 @@ export default function Page() { return (
-

+

Fumadocs Blog

Light and gorgeous. like the moon

@@ -39,14 +39,14 @@ export default function Page() {

{post.data.title}

{post.data.description}

-

+

{new Date(post.data.date ?? post.file.name).toDateString()}

diff --git a/apps/docs/utils/metadata.ts b/apps/docs/utils/metadata.ts index a65593d9c..dfcb8b207 100644 --- a/apps/docs/utils/metadata.ts +++ b/apps/docs/utils/metadata.ts @@ -25,4 +25,4 @@ export function createMetadata(override: Metadata): Metadata { export const baseUrl = process.env.NODE_ENV === 'development' ? new URL('http://localhost:3000') - : new URL(`https://${process.env.VERCEL_URL}`); + : new URL(`https://${process.env.VERCEL_URL!}`); From 812e6a6d675fdbd5234b80d554e36a8f1bb7d5ce Mon Sep 17 00:00:00 2001 From: fuma-nama Date: Wed, 15 May 2024 19:04:44 +0800 Subject: [PATCH 11/13] Fix padding problems --- packages/ui/src/components/nav.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/nav.tsx b/packages/ui/src/components/nav.tsx index 458a3447c..8db285c56 100644 --- a/packages/ui/src/components/nav.tsx +++ b/packages/ui/src/components/nav.tsx @@ -80,7 +80,7 @@ export function Nav({ : 'border-foreground/10 bg-background/50 backdrop-blur-md', )} > -