From a43ff698ee57b790d093b6d4f7fd7b030be70994 Mon Sep 17 00:00:00 2001 From: Julie <43496356+julieg18@users.noreply.github.com> Date: Thu, 30 Sep 2021 11:23:43 -0500 Subject: [PATCH] Add markdown tabs (#113) --- .../Markdown/ToggleProvider/index.tsx | 48 ++++++++++ .../pages/Documentation/Markdown/index.tsx | 92 ++++++++++++++++++- .../Documentation/Markdown/styles.module.css | 50 ++++++++++ 3 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 src/components/pages/Documentation/Markdown/ToggleProvider/index.tsx diff --git a/src/components/pages/Documentation/Markdown/ToggleProvider/index.tsx b/src/components/pages/Documentation/Markdown/ToggleProvider/index.tsx new file mode 100644 index 00000000..7d4df78c --- /dev/null +++ b/src/components/pages/Documentation/Markdown/ToggleProvider/index.tsx @@ -0,0 +1,48 @@ +import React, { createContext, useState } from 'react' + +interface ITogglesData { + [key: string]: { texts: string[]; checkedInd: number } +} + +interface ITogglesContext { + addNewToggle?: (id: string, texts: string[]) => void + updateToggleInd?: (id: string, newInd: number) => void + togglesData?: ITogglesData +} + +export const TogglesContext = createContext({}) + +export const TogglesProvider: React.FC = ({ children }) => { + const [togglesData, setTogglesData] = useState({}) + + const addNewToggle = (id: string, texts: string[]): void => { + const togglesDataCopy: ITogglesData = { ...togglesData } + togglesDataCopy[id] = { texts, checkedInd: 0 } + setTogglesData(togglesDataCopy) + } + + const updateToggleInd = (id: string, newInd: number): void => { + const togglesDataCopy: ITogglesData = { ...togglesData } + const selectedTabText = togglesDataCopy[id].texts[newInd] + togglesDataCopy[id] = { ...togglesDataCopy[id], checkedInd: newInd } + + for (const [toggleId, { texts }] of Object.entries(togglesDataCopy)) { + if (texts.includes(selectedTabText)) { + togglesDataCopy[toggleId] = { + ...togglesDataCopy[toggleId], + checkedInd: togglesDataCopy[id].texts.indexOf(selectedTabText) + } + } + } + + setTogglesData(togglesDataCopy) + } + + return ( + + {children} + + ) +} diff --git a/src/components/pages/Documentation/Markdown/index.tsx b/src/components/pages/Documentation/Markdown/index.tsx index 1c30a015..d5e3bb02 100644 --- a/src/components/pages/Documentation/Markdown/index.tsx +++ b/src/components/pages/Documentation/Markdown/index.tsx @@ -2,10 +2,13 @@ import React, { useCallback, useEffect, useRef, + useState, ReactNode, - ReactElement + ReactElement, + useContext } from 'react' import cn from 'classnames' +import { nanoid } from 'nanoid' import { navigate } from '@reach/router' import rehypeReact from 'rehype-react' import Collapsible from 'react-collapsible' @@ -17,6 +20,7 @@ import { getPathWithSource } from '../../../../utils/shared/sidebar' import sharedStyles from '../styles.module.css' import 'github-markdown-css/github-markdown.css' import styles from './styles.module.css' +import { TogglesContext, TogglesProvider } from './ToggleProvider' const isInsideCodeBlock = (node: Element): boolean => { while (node?.parentNode) { @@ -117,6 +121,84 @@ const Card: React.FC<{ ) } +const ToggleTab: React.FC<{ + id: string + title: string + ind: number + onChange: () => void + checked: boolean +}> = ({ children, id, checked, ind, onChange, title }) => { + const inputId = `tab-${id}-${ind}` + + return ( + <> + + + {children} + + ) +} + +const Toggle: React.FC<{ + children: Array<{ props: { title: string } } | string> +}> = ({ children }) => { + const [toggleId, setToggleId] = useState('') + const { + addNewToggle = (): null => null, + updateToggleInd = (): null => null, + togglesData = {} + } = useContext(TogglesContext) + const tabs: Array<{ props: { title: string } } | string> = children.filter( + child => child !== '\n' + ) + const tabsTitles = tabs.map(tab => + typeof tab === 'object' ? tab.props.title : '' + ) + + useEffect(() => { + if (toggleId === '') { + const newId = nanoid() + addNewToggle(newId, tabsTitles) + setToggleId(newId) + } + + if (toggleId && !togglesData[toggleId]) { + addNewToggle(toggleId, tabsTitles) + } + }, [togglesData]) + + return ( +
+ {tabs.map((tab, i) => ( + updateToggleInd(toggleId, i)} + > + {tab} + + ))} +
+ ) +} + +const Tab: React.FC = ({ children }) => ( +
{children}
+) + const renderAst = new rehypeReact({ // eslint-disable-next-line @typescript-eslint/no-explicit-any createElement: React.createElement as any, @@ -125,7 +207,9 @@ const renderAst = new rehypeReact({ details: Details, a: Link, card: Card, - cards: Cards + cards: Cards, + toggle: Toggle, + tab: Tab } }).Compiler @@ -192,7 +276,9 @@ const Markdown: React.FC = ({ Edit on GitHub -
{renderAst(htmlAst)}
+ +
{renderAst(htmlAst)}
+
diff --git a/src/components/pages/Documentation/Markdown/styles.module.css b/src/components/pages/Documentation/Markdown/styles.module.css index a6ed9a12..4a67fde3 100644 --- a/src/components/pages/Documentation/Markdown/styles.module.css +++ b/src/components/pages/Documentation/Markdown/styles.module.css @@ -430,3 +430,53 @@ a.card { background-color: var(--color-light-blue); } } + +.toggle { + display: flex; + flex-wrap: wrap; + + input { + height: 0; + opacity: 0; + position: absolute; + width: 0; + overflow: hidden; + } + + input:checked + label { + color: var(--color-azure); + border-color: var(--color-azure); + } + + input:checked + label + .tab { + height: initial; + opacity: initial; + position: static; + width: 100%; + overflow: visible; + } + + .tabHeading { + padding: 12px 16px 10px; + background-color: transparent; + border: none; + border-bottom: 2px solid transparent; + font-weight: bold; + font-size: 16px; + font-family: var(--font-brandon); + order: -1; + + &:hover { + cursor: pointer; + } + } +} + +.tab { + margin: 10px 0 0; + height: 0; + opacity: 0; + position: absolute; + overflow: hidden; + width: 0; +}