From c105507f2f097c17fb3b53df8b1a093b0a249d00 Mon Sep 17 00:00:00 2001 From: Julie <43496356+julieg18@users.noreply.github.com> Date: Tue, 12 Oct 2021 07:18:20 -0500 Subject: [PATCH] Add markdown tabs (#2914) --- .../Markdown/ToggleProvider/index.tsx | 48 ++++++++++ .../Documentation/Markdown/index.tsx | 94 ++++++++++++++++++- .../Documentation/Markdown/styles.module.css | 50 ++++++++++ 3 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 src/components/Documentation/Markdown/ToggleProvider/index.tsx diff --git a/src/components/Documentation/Markdown/ToggleProvider/index.tsx b/src/components/Documentation/Markdown/ToggleProvider/index.tsx new file mode 100644 index 0000000000..7d4df78c6c --- /dev/null +++ b/src/components/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/Documentation/Markdown/index.tsx b/src/components/Documentation/Markdown/index.tsx index fc76f76243..f7881d23a5 100644 --- a/src/components/Documentation/Markdown/index.tsx +++ b/src/components/Documentation/Markdown/index.tsx @@ -1,4 +1,11 @@ -import React, { ReactNode, ReactElement } from 'react' +import React, { + useEffect, + useState, + ReactNode, + ReactElement, + useContext +} from 'react' +import { nanoid } from 'nanoid' import rehypeReact from 'rehype-react' import Collapsible from 'react-collapsible' @@ -7,6 +14,7 @@ import Link from '../../Link' import Tooltip from './Tooltip' import styles from './styles.module.css' +import { TogglesContext, TogglesProvider } from './ToggleProvider' const Details: React.FC<{ children: Array<{ props: { children: ReactNode } } | string> @@ -95,6 +103,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, @@ -104,7 +190,9 @@ const renderAst = new rehypeReact({ abbr: Abbr, a: Link, card: Card, - cards: Cards + cards: Cards, + toggle: Toggle, + tab: Tab } }).Compiler @@ -125,7 +213,7 @@ const Markdown: React.FC = ({ }) => { return (
- {renderAst(htmlAst)} + {renderAst(htmlAst)}
) } diff --git a/src/components/Documentation/Markdown/styles.module.css b/src/components/Documentation/Markdown/styles.module.css index 08f999dfb3..7c8f0f591c 100644 --- a/src/components/Documentation/Markdown/styles.module.css +++ b/src/components/Documentation/Markdown/styles.module.css @@ -118,3 +118,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; +}