1
0
mirror of https://github.com/tabler/tabler.git synced 2026-08-09 21:02:22 +04:00

docs improvements

This commit is contained in:
codecalm
2023-07-27 01:16:23 +02:00
parent 3ff30bc667
commit e210294146
15 changed files with 299 additions and 143 deletions
@@ -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 = { export const metadata = {
title: 'Documentation', title: 'Documentation',
template: '%s - Documentation', template: '%s - Documentation',
@@ -13,23 +5,10 @@ export const metadata = {
}; };
export default function DocsLayout({ children /*, meta = {}, pageProps*/ }) { export default function DocsLayout({ children /*, meta = {}, pageProps*/ }) {
// const docsMenu = getDocsMenu(),
// router = useRouter(),
// category = getCategory(router.pathname),
// [prev, next] = getPrevNext(router.pathname)
return ( return (
<div className="border-bottom border-top"> <div className="border-bottom border-top">
<div className="container"> <div className="container">
<div className="row g-0"> {children}
<div className="md:col-auto docs-side">
{/*<input type="search" className="form-control w-100 mb-5" placeholder="Search&hellip;" />*/}
<DocsMenu />
</div>
<div className="md:col">
<div className="py-6 md:pl-6">{children}</div>
</div>
</div>
</div> </div>
</div> </div>
); );
+52 -87
View File
@@ -4,10 +4,13 @@ import { notFound } from 'next/navigation';
import { allDocs } from 'contentlayer/generated'; import { allDocs } from 'contentlayer/generated';
import { name } from '@/config/site'; import { name } from '@/config/site';
import { getTableOfContents } from '@/lib/toc';
import Mdx from '@/components/MDX'; import Mdx from '@/components/MDX';
import TablerSponsorsBanner from '@/components/TablerSponsorsBanner'; import TablerSponsorsBanner from '@/components/TablerSponsorsBanner';
import Link from 'next/link'; import Link from 'next/link';
import TOC from '@/components/TOC';
import DocsMenu from '@/components/DocsMenu';
import Icon from '@/components/Icon';
interface DocPageProps { interface DocPageProps {
params: { params: {
@@ -59,100 +62,62 @@ export default async function DocPage({ params }: DocPageProps) {
notFound(); notFound();
} }
// const toc = await getTableOfContents(doc.body.raw) const toc = await getTableOfContents(doc.body.raw);
return ( return (
<> <div className="row g-0">
<nav aria-label="breadcrumbs" className="breadcrumb mb-6"> <div className="md:col-auto docs-side">
<ul className="breadcrumb-list"> {/*<input type="search" className="form-control w-100 mb-5" placeholder="Search&hellip;" />*/}
<li className="breadcrumb-item"> <DocsMenu />
<Link href="/" className="breadcrumb-link"> </div>
Home <div className="md:col">
</Link> <div className="py-6 md:pl-6">
</li> <nav aria-label="breadcrumbs" className="breadcrumb mb-6">
<li className="breadcrumb-item"> <ul className="breadcrumb-list">
<Link href="/docs" className="breadcrumb-link"> <li className="breadcrumb-item">
Documentation <Link href="/" className="breadcrumb-link">
</Link> Home
</li> </Link>
<li className="breadcrumb-item"> </li>
<Link href={`/docs/${params.slug.join('/')}`} className="breadcrumb-link"> <li className="breadcrumb-item">
{doc.title} <Link href="/docs" className="breadcrumb-link">
</Link> Documentation
</li> </Link>
</ul> </li>
</nav> <li className="breadcrumb-item">
<div className="markdown"> <Link href={`/docs/${params.slug.join('/')}`} className="breadcrumb-link">
{/* {category && ( {doc.title}
</Link>
</li>
</ul>
</nav>
<div className="markdown">
{/* {category && (
<div className="h-subheader text-primary">{category}</div> <div className="h-subheader text-primary">{category}</div>
)} */} )} */}
{doc.title && <h1>{doc.title}</h1>} {doc.title && <h1>{doc.title}</h1>}
{doc.description && <p className="lead">{doc.description}</p>} {doc.description && <p className="lead">{doc.description}</p>}
<Mdx code={doc.body.code} /> <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>
)} */}
</div> </div>
<div className="md:col">
{/* {next && ( <TablerSponsorsBanner className="mt-7" />
<Link href={next.url} className="card text-right"> </div>
<div className="card-body p-3"> </div>
<div className="row items-center g-3"> <div className="docs-side-toc">
<div className="col"> <div className="pl-6 font-h6 pt-6">
<div className="h-subheader mb-1"> <div className="h6 mb-3">On this page</div>
{next.category} <div>
</div> <TOC toc={toc} />
<div className="h5 m-0">{next.title}</div> </div>
</div> <div className="mt-4 border-top pt-4">
<div className="col-auto"> <a href={`https://github.com/tabler/tabler/blob/main/docs/${doc.slugAsParams.split(',').join('/')}.mdx`} className="link-muted">
<div className="card-chevron"> Improve this page
<Icon name="chevron-right" /> <Icon name="edit" className="icon-inline ml-2" />
</div> </a>
</div>
</div>
</div>
</Link>
)} */}
</div> </div>
</div> </div>
</div> </div>
{/* // <main className="relative py-6 lg:gap-10 lg:py-10 xl:grid xl:grid-cols-[1fr_300px]"> </div>
// <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> */}
</>
); );
} }
+2
View File
@@ -653,3 +653,5 @@ export default async function HomePage() {
</> </>
); );
} }
HomePage.bodyClass = 'xxx';
+2 -1
View File
@@ -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 ( return (
<html lang="en"> <html lang="en">
<head> <head>
@@ -74,6 +74,7 @@ export default function RootLayout({ children }: { children: React.ReactNode; })
)} )}
</head> </head>
<body> <body>
{bodyClass}
<PageProgress /> <PageProgress />
{children} {children}
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> <link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
+101
View File
@@ -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;
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { uiDownloadUrl } from '@/config/site';
export default function LayoutHeroUi() { export default function LayoutHeroUi() {
return ( return (
<> <>
<header className="hero pt-10 pb-0"> <header className="hero pt-10 pb-0 bg-gradient">
<div className="container"> <div className="container">
<h1 className="hero-title" data-aos="zoom-y-out"> <h1 className="hero-title" data-aos="zoom-y-out">
Develop beautiful web apps with&nbsp;Tabler Develop beautiful web apps with&nbsp;Tabler
+7 -7
View File
@@ -1,13 +1,13 @@
import React from "react"; import React from 'react';
const useMounted = () => { const useMounted = () => {
const [mounted, setMounted] = React.useState(false) const [mounted, setMounted] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
setMounted(true) setMounted(true);
}, []) }, []);
return mounted return mounted;
} };
export default useMounted export default useMounted;
+11 -15
View File
@@ -1,23 +1,19 @@
import { RefObject } from 'react' import { RefObject } from 'react';
import useEventListener from './use-event-listener' import useEventListener from './use-event-listener';
type Handler = (event: MouseEvent) => void type Handler = (event: MouseEvent) => void;
const useOnClickOutside = <T extends HTMLElement = HTMLElement>( const useOnClickOutside = <T extends HTMLElement = HTMLElement>(ref: RefObject<T>, handler: Handler, mouseEvent: 'mousedown' | 'mouseup' = 'mousedown'): void => {
ref: RefObject<T>, useEventListener(mouseEvent, (event) => {
handler: Handler, const el = ref?.current;
mouseEvent: 'mousedown' | 'mouseup' = 'mousedown',
): void => {
useEventListener(mouseEvent, event => {
const el = ref?.current
// Do nothing if clicking ref's element or descendent elements // Do nothing if clicking ref's element or descendent elements
if (!el || el.contains(event.target as Node)) { if (!el || el.contains(event.target as Node)) {
return return;
} }
handler(event) handler(event);
}) });
} };
export default useOnClickOutside export default useOnClickOutside;
+76
View File
@@ -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
View File
@@ -32,6 +32,11 @@
"@tabler/icons": "^2.28.0", "@tabler/icons": "^2.28.0",
"@tabler/icons-png": "^2.25.0", "@tabler/icons-png": "^2.25.0",
"@tabler/icons-webfont": "^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/node": "^20.4.2",
"@types/react": "18.2.15", "@types/react": "18.2.15",
"@types/react-dom": "18.2.7", "@types/react-dom": "18.2.7",
@@ -62,6 +67,8 @@
"mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdx-jsx": "^3.0.0",
"mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-mdxjs-esm": "^2.0.0",
"mdast-util-to-markdown": "^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": "^0.3.1",
"mdx-annotations": "^0.1.3", "mdx-annotations": "^0.1.3",
"minimatch": "^9.0.3", "minimatch": "^9.0.3",
@@ -105,12 +112,6 @@
"typescript": "5.1.6", "typescript": "5.1.6",
"unist-util-visit": "^5.0.0", "unist-util-visit": "^5.0.0",
"webpack": "^5.88.2", "webpack": "^5.88.2",
"yaml": "^2.3.1", "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"
} }
} }
+23
View File
@@ -142,6 +142,9 @@ dependencies:
mdast-util-to-string: mdast-util-to-string:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0 version: 4.0.0
mdast-util-toc:
specifier: ^7.0.0
version: 7.0.0
mdx: mdx:
specifier: ^0.3.1 specifier: ^0.3.1
version: 0.3.1 version: 0.3.1
@@ -3004,6 +3007,10 @@ packages:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
dev: false dev: false
/@types/ungap__structured-clone@0.3.0:
resolution: {integrity: sha512-eBWREUhVUGPze+bUW22AgUr05k8u+vETzuYdLYSvWqGTUe0KOf+zVnOB1qER5wMcw8V6D9Ar4DfJmVvD1yu0kQ==}
dev: false
/@types/unist@2.0.6: /@types/unist@2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false dev: false
@@ -3074,6 +3081,10 @@ packages:
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: false dev: false
/@ungap/structured-clone@1.2.0:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: false
/@vercel/analytics@1.0.1: /@vercel/analytics@1.0.1:
resolution: {integrity: sha512-Ux0c9qUfkcPqng3vrR0GTrlQdqNJ2JREn/2ydrVuKwM3RtMfF2mWX31Ijqo1opSjNAq6rK76PwtANw6kl6TAow==} resolution: {integrity: sha512-Ux0c9qUfkcPqng3vrR0GTrlQdqNJ2JREn/2ydrVuKwM3RtMfF2mWX31Ijqo1opSjNAq6rK76PwtANw6kl6TAow==}
dev: false dev: false
@@ -6100,6 +6111,18 @@ packages:
unist-util-visit: 4.1.2 unist-util-visit: 4.1.2
dev: false 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: /mdn-data@2.0.28:
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
dev: false dev: false
+1
View File
@@ -222,6 +222,7 @@ $utilities: (
class: text, class: text,
values: map-merge($colors, ( values: map-merge($colors, (
muted: var(--color-muted, #{$color-muted}), muted: var(--color-muted, #{$color-muted}),
headers: var(--color-headers, #{$color-headers}),
reset: inherit reset: inherit
)) ))
), ),
+13 -1
View File
@@ -8,7 +8,7 @@
height: calc(100vh - #{$header-height}); height: calc(100vh - #{$header-height});
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 15rem; width: 13rem;
&:after { &:after {
content: ''; 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,
.docs-menu-group, .docs-menu-group,
.docs-menu-submenu { .docs-menu-submenu {
+1 -1
View File
@@ -34,7 +34,7 @@
.icon-inline { .icon-inline {
width: 1.2em; width: 1.2em;
height: 1.2em; height: 1.2em;
stroke-width: 2; stroke-width: 1.5;
stroke: currentColor; stroke: currentColor;
vertical-align: sub; vertical-align: sub;
} }
-1
View File
@@ -88,7 +88,6 @@ h5,
h6, h6,
.h6 { .h6 {
@extend %headers-common; @extend %headers-common;
font-weight: $font-weight-normal;
font-size: $font-size-h6; font-size: $font-size-h6;
line-height: $line-height-h6; line-height: $line-height-h6;
margin-bottom: $gap-1; margin-bottom: $gap-1;