adjust content layout

This commit is contained in:
Hossein Mehrabi 2022-10-07 10:15:58 +03:30
parent 08fe532bbe
commit 14316c75b1
21 changed files with 671 additions and 52 deletions

View File

@ -4,6 +4,8 @@ sidebar_position: 1
# Tutorial Intro
import HomepageFeatures from '@site/src/components/HomepageFeatures';
Let's discover **Docusaurus in less than 5 minutes**.
## Getting Started
@ -45,3 +47,6 @@ The `cd` command changes the directory you're working with. In order to work wit
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.

View File

@ -1,53 +1,53 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require("prism-react-renderer/themes/github");
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
const lightCodeTheme = require('prism-react-renderer/themes/github')
const darkCodeTheme = require('prism-react-renderer/themes/dracula')
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "My Site",
tagline: "Dinosaurs are cool",
url: "https://your-docusaurus-test-site.com",
baseUrl: "/",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
favicon: "img/favicon.ico",
title: 'My Site',
tagline: 'Dinosaurs are cool',
url: 'https://your-docusaurus-test-site.com',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: "facebook", // Usually your GitHub org/user name.
projectName: "docusaurus", // Usually your repo name.
organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name.
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: "en",
locales: ["en"],
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
"classic",
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve("./sidebars.js"),
sidebarPath: require.resolve('./sidebars.js'),
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
blog: {
showReadingTime: true,
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
@ -56,66 +56,67 @@ const config = {
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
docs: { sidebar: { hideable: true } },
navbar: {
title: "My Site",
title: 'My Site',
logo: {
alt: "My Site Logo",
src: "img/logo.svg",
alt: 'My Site Logo',
src: 'img/logo.svg',
},
items: [
{
type: "doc",
docId: "intro",
position: "left",
label: "Tutorial",
type: 'doc',
docId: 'intro',
position: 'left',
label: 'Tutorial',
},
{ to: "/blog", label: "Blog", position: "left" },
{ to: '/blog', label: 'Blog', position: 'left' },
{
href: "https://github.com/facebook/docusaurus",
label: "GitHub",
position: "right",
href: 'https://github.com/facebook/docusaurus',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: "dark",
style: 'dark',
links: [
{
title: "Docs",
title: 'Docs',
items: [
{
label: "Tutorial",
to: "/docs/intro",
label: 'Tutorial',
to: '/docs/intro',
},
],
},
{
title: "Community",
title: 'Community',
items: [
{
label: "Stack Overflow",
href: "https://stackoverflow.com/questions/tagged/docusaurus",
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
},
{
label: "Discord",
href: "https://discordapp.com/invite/docusaurus",
label: 'Discord',
href: 'https://discordapp.com/invite/docusaurus',
},
{
label: "Twitter",
href: "https://twitter.com/docusaurus",
label: 'Twitter',
href: 'https://twitter.com/docusaurus',
},
],
},
{
title: "More",
title: 'More',
items: [
{
label: "Blog",
to: "/blog",
label: 'Blog',
to: '/blog',
},
{
label: "GitHub",
href: "https://github.com/facebook/docusaurus",
label: 'GitHub',
href: 'https://github.com/facebook/docusaurus',
},
],
},
@ -127,7 +128,7 @@ const config = {
darkTheme: darkCodeTheme,
},
}),
plugins: ["docusaurus-plugin-sass"],
};
plugins: ['docusaurus-plugin-sass'],
}
module.exports = config;
module.exports = config

View File

@ -14,7 +14,7 @@
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
// But you can create a sidebar manually
/*
@ -28,6 +28,6 @@ const sidebars = {
},
],
*/
};
}
module.exports = sidebars;
module.exports = sidebars

View File

@ -0,0 +1,28 @@
import { Store as StoreBase } from '@site/src/lib/store'
export namespace GlobalStore {
export type State = {
hiddenSidebar: boolean
}
export type Actions = {
setHiddenSidebar: (payload: any) => void
}
export class Store extends StoreBase<State, Actions, []> {}
}
export const globalStore = new GlobalStore.Store(() => {
return {
setHiddenSidebar: (payload, state, setState, dispatch) => {
setState((state) => {
state.hiddenSidebar =
typeof payload === 'function' ? payload(state.hiddenSidebar) : payload
})
},
}
}, [])
export const selectHiddenSidebar = globalStore.selector(
(state) => state.hiddenSidebar,
)

View File

@ -0,0 +1 @@
export * from './GlobalStore'

36
src/css/_utils.scss Normal file
View File

@ -0,0 +1,36 @@
@use './vars';
@use 'sass:map';
@use 'sass:list';
@mixin responsive($device-size, $function: 'exact') {
$index: index(vars.$device-sizes, $device-size);
$bounds: map.get(vars.$breakpoints, $device-size);
$min-width: list.nth($bounds, 1);
$max-width: list.nth($bounds, 2);
$query: '';
@if $function == 'exact' {
$query: '(min-width: #{$min-width}px)';
@if $max-width != null {
$query: $query + ' and (max-width: #{$max-width}px)';
}
} @else if $function == 'up' {
@if $min-width != null {
$query: '(min-width: #{$min-width}px)';
} @else {
$query: '(min-width: 0px)';
}
} @else if $function == 'down' {
@if $min-width != null {
$query: '(max-width: #{$min-width - 1}px)';
} @else {
$query: '(max-width: #{$max-width - 1}px)';
}
}
@media #{$query} {
@content;
}
}

23
src/css/_vars.scss Normal file
View File

@ -0,0 +1,23 @@
$breakpoints: (
'xs': (
null,
575,
),
'sm': (
576,
767,
),
'md': (
768,
996,
),
'lg': (
997,
1199,
),
'xl': (
1200,
),
);
$device-sizes: 'xs', 'sm', 'md', 'lg', 'xl';

View File

@ -18,7 +18,7 @@
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme="dark"] {
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
@ -28,3 +28,8 @@
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
:root {
--doc-sidebar-width: 16.66vw !important;
--doc-sidebar-max-width: 320px;
}

10
src/lib/array.utils.ts Normal file
View File

@ -0,0 +1,10 @@
export const updateArray = <T>(
arr: T[],
from: number,
to: number,
update: (element: T, index: number) => any,
): T[] => [
...arr.slice(0, from),
...arr.slice(from, to + 1).map((val, index) => update(val, index + from)),
...arr.slice(to + 1),
]

14
src/lib/store/Hook.tsx Normal file
View File

@ -0,0 +1,14 @@
import React, { useEffect } from 'react'
export const Hook: React.FC<{
hook: Function
onChange: (value: any) => void
}> = ({ hook: useHook, onChange }) => {
const value = useHook()
useEffect(() => {
onChange(value)
}, [value])
return <></>
}

1
src/lib/store/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './store'

137
src/lib/store/store.tsx Normal file
View File

@ -0,0 +1,137 @@
import produce from 'immer'
import React, { useContext, useMemo, useRef, useState } from 'react'
import { updateArray } from '../array.utils'
import { Hook } from './Hook'
type SetState<S = any> = (value: S | ((currentState: S) => void)) => void
type StoreType<S = any, D = any> = {
state: S
setState: SetState<S>
dispatch: D
}
type Dispatch<T> = (payload: T) => void
type DispatchActions<T = any> = {
[key: string]: Dispatch<T>
}
type StoreActions<
S,
A extends DispatchActions<any>,
I extends Array<any> = [],
> = {
[K in keyof A]: (
payload: Parameters<A[K]>[0],
state: S,
setState: SetState<S>,
dispatch: A,
...injected: I
) => void
}
export class Store<
S = {},
A extends DispatchActions<any> = {},
I extends any[] | [] = [],
> {
stateRef: React.MutableRefObject<S>
setStateRef: React.MutableRefObject<SetState<S>>
injectedRef: React.MutableRefObject<Array<any>>
private context: React.Context<StoreType<S, A>>
constructor(
private actions: () => StoreActions<S, A, I>,
private inject: any[] = [],
) {}
public useCreateStore = (initialState: S): StoreType<S> => {
const [state, _setState] = useState(initialState)
const setState: SetState = (value) =>
typeof value === 'function'
? _setState(produce<S>(value))
: _setState(value)
const dispatch = useMemo(() => this.createDispatch(), [])
return { state, setState, dispatch }
}
public Provider: React.FC<React.PropsWithChildren<{ store: StoreType<S> }>> =
({ children, store }) => {
this.stateRef = useRef()
this.setStateRef = useRef()
this.injectedRef = useRef(this.inject.map((hook) => null))
if (!this.context) {
this.context = React.createContext(null as any)
}
const P = this.context.Provider
this.stateRef.current = store.state
this.setStateRef.current = store.setState
return (
<P value={store}>
{children}
{this.inject.map((hook, index) => (
<Hook
key={index}
hook={hook}
onChange={(value) => {
this.injectedRef.current = updateArray(
this.injectedRef.current,
index,
index,
() => value,
)
}}
/>
))}
</P>
)
}
public useStore = () => {
return useContext(this.context)
}
public useDispatch = () => this.useStore().dispatch
useSelector<T = any>(selector: (state: S) => T): T {
const { state } = this.useStore()
const value = selector(state)
return useMemo<T>(() => value, [value])
}
selector<T = any>(selector: (state: S) => T) {
return selector
}
private createDispatch = (): A => {
const dispatch = Object.fromEntries(
Object.entries(this.actions()).map(([name, func]) => [
name,
(payload: any) => {
const set = (args: any) => this.setStateRef.current(args)
func(
payload,
this.stateRef.current,
set,
dispatch,
...this.injectedRef.current,
)
},
]),
)
return dispatch as A
}
}

View File

@ -0,0 +1,69 @@
import {
PageMetadata,
useCurrentSidebarCategory,
} from '@docusaurus/theme-common'
import useBaseUrl from '@docusaurus/useBaseUrl'
import DocBreadcrumbs from '@theme/DocBreadcrumbs'
import DocCardList from '@theme/DocCardList'
import type { Props } from '@theme/DocCategoryGeneratedIndexPage'
import DocPaginator from '@theme/DocPaginator'
import DocVersionBadge from '@theme/DocVersionBadge'
import DocVersionBanner from '@theme/DocVersionBanner'
import Heading from '@theme/Heading'
import React from 'react'
import styles from './styles.module.css'
function DocCategoryGeneratedIndexPageMetadata({
categoryGeneratedIndex,
}: Props): JSX.Element {
return (
<PageMetadata
title={categoryGeneratedIndex.title}
description={categoryGeneratedIndex.description}
keywords={categoryGeneratedIndex.keywords}
// TODO `require` this?
image={useBaseUrl(categoryGeneratedIndex.image)}
/>
)
}
function DocCategoryGeneratedIndexPageContent({
categoryGeneratedIndex,
}: Props): JSX.Element {
const category = useCurrentSidebarCategory()
return (
<div className={styles.generatedIndexPage}>
<DocVersionBanner />
<DocBreadcrumbs />
<DocVersionBadge />
<header>
<Heading as="h1" className={styles.title}>
{categoryGeneratedIndex.title}
</Heading>
{categoryGeneratedIndex.description && (
<p>{categoryGeneratedIndex.description}</p>
)}
</header>
<article className="margin-top--lg">
<DocCardList items={category.items} className={styles.list} />
</article>
<footer className="margin-top--lg">
<DocPaginator
previous={categoryGeneratedIndex.navigation.previous}
next={categoryGeneratedIndex.navigation.next}
/>
</footer>
</div>
)
}
export default function DocCategoryGeneratedIndexPage(
props: Props,
): JSX.Element {
return (
<>
<DocCategoryGeneratedIndexPageMetadata {...props} />
<DocCategoryGeneratedIndexPageContent {...props} />
</>
)
}

View File

@ -0,0 +1,23 @@
.generatedIndexPage {
padding: 0 var(--ifm-spacing-horizontal);
}
@media (min-width: 997px) {
.generatedIndexPage {
max-width: 75% !important;
}
.list article:nth-last-child(-n + 2) {
margin-bottom: 0 !important;
}
}
/* Duplicated from .markdown h1 */
.title {
--ifm-h1-font-size: 3rem;
margin-bottom: calc(1.25 * var(--ifm-leading));
}
.list article:last-child {
margin-bottom: 0 !important;
}

View File

@ -0,0 +1,71 @@
import { useWindowSize } from '@docusaurus/theme-common'
import { useDoc } from '@docusaurus/theme-common/internal'
import DocBreadcrumbs from '@theme/DocBreadcrumbs'
import DocItemContent from '@theme/DocItem/Content'
import DocItemFooter from '@theme/DocItem/Footer'
import { Props } from '@theme/DocItem/Layout'
import DocItemPaginator from '@theme/DocItem/Paginator'
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop'
import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile'
import DocVersionBadge from '@theme/DocVersionBadge'
import DocVersionBanner from '@theme/DocVersionBanner'
import clsx from 'clsx'
import React from 'react'
import styles from './styles.module.scss'
/**
* Decide if the toc should be rendered, on mobile or desktop viewports
*/
function useDocTOC() {
const { frontMatter, toc } = useDoc()
const windowSize = useWindowSize()
const hidden = frontMatter.hide_table_of_contents
const canRender = !hidden && toc.length > 0
const mobile = canRender ? <DocItemTOCMobile /> : undefined
const desktop =
canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
<DocItemTOCDesktop />
) : undefined
return {
hidden,
mobile,
desktop,
}
}
export default function DocItemLayout({ children }: Props): JSX.Element {
const docTOC = useDocTOC()
const windowSize = useWindowSize()
return (
<div className={clsx(styles.root, 'row')}>
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
<DocVersionBanner />
<div className={clsx(styles.docItemContainer)}>
<div>
<article>
<DocBreadcrumbs />
<DocVersionBadge />
{docTOC.mobile}
<DocItemContent>{children}</DocItemContent>
<DocItemFooter />
</article>
<DocItemPaginator />
</div>
</div>
</div>
{windowSize !== 'mobile' && (
<aside className={clsx('col')}>footnotes</aside>
)}
{docTOC.desktop && (
<div className={clsx(styles.tocDesktopWrapper, 'col')}>
{docTOC.desktop}
</div>
)}
</div>
)
}

View File

@ -0,0 +1,45 @@
@use '@site/src/css/utils';
.root {
width: 100%;
margin: 0 !important;
}
.docItemCol {
flex-grow: 1;
min-width: 0;
}
.root > aside {
width: 25vw;
flex-basis: 25vw;
flex-grow: 0;
flex-shrink: 0;
}
.tocDesktopWrapper {
width: 16.66vw;
flex-basis: 16.66vw;
flex-grow: 0;
flex-shrink: 0;
}
.docItemContainer > div {
flex-grow: 1;
}
.docItemContainer header + *,
.docItemContainer article > *:first-child {
margin-top: 0;
}
@include utils.responsive('lg', 'down') {
.docItemContainer > div:first-child {
}
}
@media (min-width: 997px) {
.docItemCol {
max-width: 75% !important;
}
}

View File

@ -0,0 +1,32 @@
import { useDocsSidebar } from '@docusaurus/theme-common/internal'
import type { Props } from '@theme/DocPage/Layout/Main'
import clsx from 'clsx'
import React from 'react'
import styles from './styles.module.scss'
export default function DocPageLayoutMain({
hiddenSidebarContainer,
children,
}: Props): JSX.Element {
const sidebar = useDocsSidebar()
return (
<main
className={clsx(
styles.docMainContainer,
(hiddenSidebarContainer || !sidebar) && styles.docMainContainerEnhanced,
)}
>
<div className={styles.sidebarSpace} />
<div
className={clsx(
'padding-top--md padding-bottom--lg',
styles.docItemWrapper,
hiddenSidebarContainer && styles.docItemWrapperEnhanced,
)}
>
{children}
</div>
</main>
)
}

View File

@ -0,0 +1,40 @@
@use '@site/src/css/utils';
.docMainContainer {
display: flex;
width: 100%;
overflow: hidden;
}
.sidebarSpace {
width: 0;
}
@include utils.responsive('lg', 'up') {
.sidebarSpace {
transition: 0.3s;
flex-shrink: 0;
width: var(--doc-sidebar-width);
max-width: var(--doc-sidebar-max-width);
}
}
.docMainContainerEnhanced .sidebarSpace {
width: 0;
}
.docItemWrapper {
width: 100%;
}
@media (min-width: 997px) {
.docMainContainer {
flex-grow: 1;
}
.docMainContainerEnhanced {
}
.docItemWrapperEnhanced {
}
}

View File

@ -0,0 +1,46 @@
import { useDocsSidebar } from '@docusaurus/theme-common/internal'
import {
globalStore,
selectHiddenSidebar,
} from '@site/src/containers/GlobalStore'
import BackToTopButton from '@theme/BackToTopButton'
import type { Props } from '@theme/DocPage/Layout'
import DocPageLayoutMain from '@theme/DocPage/Layout/Main'
import DocPageLayoutSidebar from '@theme/DocPage/Layout/Sidebar'
import Layout from '@theme/Layout'
import React from 'react'
import styles from './styles.module.scss'
const DocPageWrapper: React.FC<React.PropsWithChildren<{}>> = ({
children,
}) => {
const sidebar = useDocsSidebar()
const dispatch = globalStore.useDispatch()
const hiddenSidebarContainer = globalStore.useSelector(selectHiddenSidebar)
const setHiddenSidebarContainer = dispatch.setHiddenSidebar
return (
<div className={styles.docPage}>
{sidebar && (
<DocPageLayoutSidebar
sidebar={sidebar.items}
hiddenSidebarContainer={hiddenSidebarContainer}
setHiddenSidebarContainer={setHiddenSidebarContainer}
/>
)}
<DocPageLayoutMain hiddenSidebarContainer={hiddenSidebarContainer}>
{children}
</DocPageLayoutMain>
</div>
)
}
export default function DocPageLayout({ children }: Props): JSX.Element {
return (
<Layout wrapperClassName={styles.docsWrapper}>
<BackToTopButton />
<DocPageWrapper>{children}</DocPageWrapper>
</Layout>
)
}

View File

@ -0,0 +1,19 @@
.docPage {
width: 100%;
position: relative;
display: grid;
& > main,
& > aside {
grid-area: 1 / 1;
}
& > aside {
max-width: var(--doc-sidebar-max-width);
}
}
.docsWrapper {
display: flex;
}

View File

@ -0,0 +1,13 @@
import { globalStore } from '@site/src/containers/GlobalStore'
import Layout from '@theme-original/Layout'
import React from 'react'
export default function LayoutWrapper(props) {
const store = globalStore.useCreateStore({ hiddenSidebar: false })
return (
<globalStore.Provider store={store}>
<Layout {...props} />
</globalStore.Provider>
)
}