mirror of
https://github.com/tabler/tabler.git
synced 2026-08-09 21:02:22 +04:00
docs improvements
This commit is contained in:
@@ -1,11 +1,3 @@
|
||||
// import Link from "next/link";
|
||||
// import { useRouter } from "next/router";
|
||||
|
||||
// import { getCategory, getDocsMenu, getPrevNext } from "@/lib/docs";
|
||||
import DocsMenu from '@/components/DocsMenu';
|
||||
|
||||
// import Icon from "@/components/Icon";
|
||||
|
||||
export const metadata = {
|
||||
title: 'Documentation',
|
||||
template: '%s - Documentation',
|
||||
@@ -13,23 +5,10 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default function DocsLayout({ children /*, meta = {}, pageProps*/ }) {
|
||||
// const docsMenu = getDocsMenu(),
|
||||
// router = useRouter(),
|
||||
// category = getCategory(router.pathname),
|
||||
// [prev, next] = getPrevNext(router.pathname)
|
||||
|
||||
return (
|
||||
<div className="border-bottom border-top">
|
||||
<div className="container">
|
||||
<div className="row g-0">
|
||||
<div className="md:col-auto docs-side">
|
||||
{/*<input type="search" className="form-control w-100 mb-5" placeholder="Search…" />*/}
|
||||
<DocsMenu />
|
||||
</div>
|
||||
<div className="md:col">
|
||||
<div className="py-6 md:pl-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,10 +4,13 @@ import { notFound } from 'next/navigation';
|
||||
import { allDocs } from 'contentlayer/generated';
|
||||
import { name } from '@/config/site';
|
||||
|
||||
import { getTableOfContents } from '@/lib/toc';
|
||||
import Mdx from '@/components/MDX';
|
||||
import TablerSponsorsBanner from '@/components/TablerSponsorsBanner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import TOC from '@/components/TOC';
|
||||
import DocsMenu from '@/components/DocsMenu';
|
||||
import Icon from '@/components/Icon';
|
||||
|
||||
interface DocPageProps {
|
||||
params: {
|
||||
@@ -59,100 +62,62 @@ export default async function DocPage({ params }: DocPageProps) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// const toc = await getTableOfContents(doc.body.raw)
|
||||
const toc = await getTableOfContents(doc.body.raw);
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav aria-label="breadcrumbs" className="breadcrumb mb-6">
|
||||
<ul className="breadcrumb-list">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href="/" className="breadcrumb-link">
|
||||
Home
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item">
|
||||
<Link href="/docs" className="breadcrumb-link">
|
||||
Documentation
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={`/docs/${params.slug.join('/')}`} className="breadcrumb-link">
|
||||
{doc.title}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="markdown">
|
||||
{/* {category && (
|
||||
<div className="row g-0">
|
||||
<div className="md:col-auto docs-side">
|
||||
{/*<input type="search" className="form-control w-100 mb-5" placeholder="Search…" />*/}
|
||||
<DocsMenu />
|
||||
</div>
|
||||
<div className="md:col">
|
||||
<div className="py-6 md:pl-6">
|
||||
<nav aria-label="breadcrumbs" className="breadcrumb mb-6">
|
||||
<ul className="breadcrumb-list">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href="/" className="breadcrumb-link">
|
||||
Home
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item">
|
||||
<Link href="/docs" className="breadcrumb-link">
|
||||
Documentation
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={`/docs/${params.slug.join('/')}`} className="breadcrumb-link">
|
||||
{doc.title}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="markdown">
|
||||
{/* {category && (
|
||||
<div className="h-subheader text-primary">{category}</div>
|
||||
)} */}
|
||||
{doc.title && <h1>{doc.title}</h1>}
|
||||
{doc.description && <p className="lead">{doc.description}</p>}
|
||||
{doc.title && <h1>{doc.title}</h1>}
|
||||
{doc.description && <p className="lead">{doc.description}</p>}
|
||||
|
||||
<Mdx code={doc.body.code} />
|
||||
</div>
|
||||
|
||||
<TablerSponsorsBanner className="mt-7" />
|
||||
|
||||
<div className="pt-7">
|
||||
<div className="row">
|
||||
<div className="md:col">
|
||||
{/* {prev && (
|
||||
<Link href={prev.url} className="card">
|
||||
<div className="card-body p-3">
|
||||
<div className="row items-center g-3">
|
||||
<div className="col-auto">
|
||||
<div className="card-chevron card-chevron-left">
|
||||
<Icon name="chevron-left" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="h-subheader mb-1">
|
||||
{prev.category}
|
||||
</div>
|
||||
<div className="h5 m-0">{prev.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)} */}
|
||||
<Mdx code={doc.body.code} />
|
||||
</div>
|
||||
<div className="md:col">
|
||||
{/* {next && (
|
||||
<Link href={next.url} className="card text-right">
|
||||
<div className="card-body p-3">
|
||||
<div className="row items-center g-3">
|
||||
<div className="col">
|
||||
<div className="h-subheader mb-1">
|
||||
{next.category}
|
||||
</div>
|
||||
<div className="h5 m-0">{next.title}</div>
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<div className="card-chevron">
|
||||
<Icon name="chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)} */}
|
||||
|
||||
<TablerSponsorsBanner className="mt-7" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="docs-side-toc">
|
||||
<div className="pl-6 font-h6 pt-6">
|
||||
<div className="h6 mb-3">On this page</div>
|
||||
<div>
|
||||
<TOC toc={toc} />
|
||||
</div>
|
||||
<div className="mt-4 border-top pt-4">
|
||||
<a href={`https://github.com/tabler/tabler/blob/main/docs/${doc.slugAsParams.split(',').join('/')}.mdx`} className="link-muted">
|
||||
Improve this page
|
||||
<Icon name="edit" className="icon-inline ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* // <main className="relative py-6 lg:gap-10 lg:py-10 xl:grid xl:grid-cols-[1fr_300px]">
|
||||
// <div className="mx-auto w-full min-w-0">
|
||||
// {/* <DocsPageHeader heading={doc.title} text={doc.description} />
|
||||
// <Mdx code={doc.body.code} />
|
||||
// <hr className="my-4 md:my-6" />
|
||||
// {/* <DocsPager doc={doc} />
|
||||
// </div>
|
||||
// <div className="hidden text-sm xl:block">
|
||||
// <div className="sticky top-16 -mt-10 max-h-[calc(var(--vh)-4rem)] overflow-y-auto pt-10">
|
||||
// {/* <DashboardTableOfContents toc={toc} />
|
||||
// </div>
|
||||
// </div>
|
||||
// </main> */}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -653,3 +653,5 @@ export default async function HomePage() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
HomePage.bodyClass = 'xxx';
|
||||
|
||||
+2
-1
@@ -62,7 +62,7 @@ export const metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode; }) {
|
||||
export default function RootLayout({ children, bodyClass }: { children: React.ReactNode; bodyClass?: string }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -74,6 +74,7 @@ export default function RootLayout({ children }: { children: React.ReactNode; })
|
||||
)}
|
||||
</head>
|
||||
<body>
|
||||
{bodyClass}
|
||||
<PageProgress />
|
||||
{children}
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { TableOfContents } from '@/lib/toc';
|
||||
import useMounted from '@/hooks/use-mounted';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface TocProps {
|
||||
toc: TableOfContents;
|
||||
}
|
||||
|
||||
export default function TOC({ toc }: TocProps) {
|
||||
const itemIds = React.useMemo(
|
||||
() =>
|
||||
toc.items
|
||||
? toc.items
|
||||
.flatMap((item) => [item.url, item?.items?.map((item) => item.url)])
|
||||
.flat()
|
||||
.filter(Boolean)
|
||||
.map((id) => id?.split('#')[1])
|
||||
: [],
|
||||
[toc],
|
||||
);
|
||||
const activeHeading = useActiveItem(itemIds);
|
||||
const mounted = useMounted();
|
||||
|
||||
if (!toc?.items) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mounted ? (
|
||||
<Tree tree={toc} activeItem={activeHeading} />
|
||||
) : null;
|
||||
}
|
||||
|
||||
function useActiveItem(itemIds: (string | undefined)[]) {
|
||||
const [activeId, setActiveId] = React.useState<string>('');
|
||||
|
||||
React.useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveId(entry.target.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: '0% 0% -80% 0%' },
|
||||
);
|
||||
|
||||
itemIds?.forEach((id) => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
observer.observe(element);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
itemIds?.forEach((id) => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
observer.unobserve(element);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [itemIds]);
|
||||
|
||||
return activeId;
|
||||
}
|
||||
|
||||
interface TreeProps {
|
||||
tree: TableOfContents;
|
||||
level?: number;
|
||||
activeItem?: string | null;
|
||||
}
|
||||
|
||||
function Tree({ tree, level = 1, activeItem }: TreeProps) {
|
||||
return tree?.items?.length && level < 3 ? (
|
||||
<ul className={clsx('p-0 list-unstyled space-y-2', { 'pl-4': level !== 1 })}>
|
||||
{tree.items.map((item, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
<a href={item.url} className={clsx('link-muted', item.url === `#${activeItem}` ? 'font-medium text-headers' : '')}>
|
||||
{item.title}
|
||||
</a>
|
||||
{item.items?.length ? <Tree tree={item} level={level + 1} activeItem={activeItem} /> : null}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : null;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { uiDownloadUrl } from '@/config/site';
|
||||
export default function LayoutHeroUi() {
|
||||
return (
|
||||
<>
|
||||
<header className="hero pt-10 pb-0">
|
||||
<header className="hero pt-10 pb-0 bg-gradient">
|
||||
<div className="container">
|
||||
<h1 className="hero-title" data-aos="zoom-y-out">
|
||||
Develop beautiful web apps with Tabler
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
const useMounted = () => {
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
return mounted
|
||||
}
|
||||
return mounted;
|
||||
};
|
||||
|
||||
export default useMounted
|
||||
export default useMounted;
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import { RefObject } from 'react'
|
||||
import useEventListener from './use-event-listener'
|
||||
import { RefObject } from 'react';
|
||||
import useEventListener from './use-event-listener';
|
||||
|
||||
type Handler = (event: MouseEvent) => void
|
||||
type Handler = (event: MouseEvent) => void;
|
||||
|
||||
const useOnClickOutside = <T extends HTMLElement = HTMLElement>(
|
||||
ref: RefObject<T>,
|
||||
handler: Handler,
|
||||
mouseEvent: 'mousedown' | 'mouseup' = 'mousedown',
|
||||
): void => {
|
||||
useEventListener(mouseEvent, event => {
|
||||
const el = ref?.current
|
||||
const useOnClickOutside = <T extends HTMLElement = HTMLElement>(ref: RefObject<T>, handler: Handler, mouseEvent: 'mousedown' | 'mouseup' = 'mousedown'): void => {
|
||||
useEventListener(mouseEvent, (event) => {
|
||||
const el = ref?.current;
|
||||
|
||||
// Do nothing if clicking ref's element or descendent elements
|
||||
if (!el || el.contains(event.target as Node)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
handler(event)
|
||||
})
|
||||
}
|
||||
handler(event);
|
||||
});
|
||||
};
|
||||
|
||||
export default useOnClickOutside
|
||||
export default useOnClickOutside;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { toc } from 'mdast-util-toc';
|
||||
import { remark } from 'remark';
|
||||
import { visit } from 'unist-util-visit';
|
||||
|
||||
const textTypes = ['text', 'emphasis', 'strong', 'inlineCode'];
|
||||
|
||||
function flattenNode(node: any) {
|
||||
const p: any[] = [];
|
||||
visit(node, (node) => {
|
||||
if (!textTypes.includes(node.type)) {
|
||||
return;
|
||||
}
|
||||
p.push(node.value);
|
||||
});
|
||||
return p.join('');
|
||||
}
|
||||
|
||||
interface Item {
|
||||
title: string;
|
||||
url: string;
|
||||
items?: Item[];
|
||||
}
|
||||
|
||||
interface Items {
|
||||
items?: Item[];
|
||||
}
|
||||
|
||||
function getItems(node, current): Items {
|
||||
if (!node) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (node.type === 'paragraph') {
|
||||
visit(node, (item) => {
|
||||
if (item.type === 'link') {
|
||||
current.url = item.url;
|
||||
current.title = flattenNode(node);
|
||||
}
|
||||
|
||||
if (item.type === 'text') {
|
||||
current.title = flattenNode(node);
|
||||
}
|
||||
});
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
if (node.type === 'list') {
|
||||
current.items = node.children.map((i) => getItems(i, {}));
|
||||
|
||||
return current;
|
||||
} else if (node.type === 'listItem') {
|
||||
const heading = getItems(node.children[0], {});
|
||||
|
||||
if (node.children.length > 1) {
|
||||
getItems(node.children[1], heading);
|
||||
}
|
||||
|
||||
return heading;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const getToc = () => (node, file) => {
|
||||
const table = toc(node);
|
||||
file.data = getItems(table.map, {});
|
||||
};
|
||||
|
||||
export type TableOfContents = Items;
|
||||
|
||||
export async function getTableOfContents(content: string): Promise<TableOfContents> {
|
||||
const result = await remark().use(getToc).process(content);
|
||||
|
||||
return result.data;
|
||||
}
|
||||
+8
-7
@@ -32,6 +32,11 @@
|
||||
"@tabler/icons": "^2.28.0",
|
||||
"@tabler/icons-png": "^2.25.0",
|
||||
"@tabler/icons-webfont": "^2.25.0",
|
||||
"@types/debug": "^4.1.8",
|
||||
"@types/eslint": "^8.44.0",
|
||||
"@types/eslint-scope": "^3.7.4",
|
||||
"@types/estree-jsx": "^1.0.0",
|
||||
"@types/extend": "^3.0.1",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/react-dom": "18.2.7",
|
||||
@@ -62,6 +67,8 @@
|
||||
"mdast-util-mdx-jsx": "^3.0.0",
|
||||
"mdast-util-mdxjs-esm": "^2.0.0",
|
||||
"mdast-util-to-markdown": "^2.0.0",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"mdast-util-toc": "^7.0.0",
|
||||
"mdx": "^0.3.1",
|
||||
"mdx-annotations": "^0.1.3",
|
||||
"minimatch": "^9.0.3",
|
||||
@@ -105,12 +112,6 @@
|
||||
"typescript": "5.1.6",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"webpack": "^5.88.2",
|
||||
"yaml": "^2.3.1",
|
||||
"@types/debug": "^4.1.8",
|
||||
"@types/eslint": "^8.44.0",
|
||||
"@types/eslint-scope": "^3.7.4",
|
||||
"@types/estree-jsx": "^1.0.0",
|
||||
"@types/extend": "^3.0.1",
|
||||
"mdast-util-to-string": "^4.0.0"
|
||||
"yaml": "^2.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+23
@@ -142,6 +142,9 @@ dependencies:
|
||||
mdast-util-to-string:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
mdast-util-toc:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
mdx:
|
||||
specifier: ^0.3.1
|
||||
version: 0.3.1
|
||||
@@ -3004,6 +3007,10 @@ packages:
|
||||
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
|
||||
dev: false
|
||||
|
||||
/@types/ungap__structured-clone@0.3.0:
|
||||
resolution: {integrity: sha512-eBWREUhVUGPze+bUW22AgUr05k8u+vETzuYdLYSvWqGTUe0KOf+zVnOB1qER5wMcw8V6D9Ar4DfJmVvD1yu0kQ==}
|
||||
dev: false
|
||||
|
||||
/@types/unist@2.0.6:
|
||||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: false
|
||||
@@ -3074,6 +3081,10 @@ packages:
|
||||
eslint-visitor-keys: 3.3.0
|
||||
dev: false
|
||||
|
||||
/@ungap/structured-clone@1.2.0:
|
||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||
dev: false
|
||||
|
||||
/@vercel/analytics@1.0.1:
|
||||
resolution: {integrity: sha512-Ux0c9qUfkcPqng3vrR0GTrlQdqNJ2JREn/2ydrVuKwM3RtMfF2mWX31Ijqo1opSjNAq6rK76PwtANw6kl6TAow==}
|
||||
dev: false
|
||||
@@ -6100,6 +6111,18 @@ packages:
|
||||
unist-util-visit: 4.1.2
|
||||
dev: false
|
||||
|
||||
/mdast-util-toc@7.0.0:
|
||||
resolution: {integrity: sha512-C28UcSqjmnWuvgT8d97qpaItHKvySqVPAECUzqQ51xuMyNFFJwcFoKW77KoMjtXrclTidLQFDzLUmTmrshRweA==}
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.0
|
||||
'@types/ungap__structured-clone': 0.3.0
|
||||
'@ungap/structured-clone': 1.2.0
|
||||
github-slugger: 2.0.0
|
||||
mdast-util-to-string: 4.0.0
|
||||
unist-util-is: 6.0.0
|
||||
unist-util-visit: 5.0.0
|
||||
dev: false
|
||||
|
||||
/mdn-data@2.0.28:
|
||||
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
|
||||
dev: false
|
||||
|
||||
@@ -222,6 +222,7 @@ $utilities: (
|
||||
class: text,
|
||||
values: map-merge($colors, (
|
||||
muted: var(--color-muted, #{$color-muted}),
|
||||
headers: var(--color-headers, #{$color-headers}),
|
||||
reset: inherit
|
||||
))
|
||||
),
|
||||
|
||||
+13
-1
@@ -8,7 +8,7 @@
|
||||
height: calc(100vh - #{$header-height});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 15rem;
|
||||
width: 13rem;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
@@ -22,6 +22,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.docs-side-toc {
|
||||
width: 15rem;
|
||||
position: sticky;
|
||||
top: calc(#{$header-height} + 1px);
|
||||
height: calc(100vh - #{$header-height});
|
||||
display: none;
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-menu,
|
||||
.docs-menu-group,
|
||||
.docs-menu-submenu {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
.icon-inline {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
stroke-width: 2;
|
||||
stroke-width: 1.5;
|
||||
stroke: currentColor;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@ h5,
|
||||
h6,
|
||||
.h6 {
|
||||
@extend %headers-common;
|
||||
font-weight: $font-weight-normal;
|
||||
font-size: $font-size-h6;
|
||||
line-height: $line-height-h6;
|
||||
margin-bottom: $gap-1;
|
||||
|
||||
Reference in New Issue
Block a user