Skip to content

Commit

Permalink
Add markdown tabs (#2914)
Browse files Browse the repository at this point in the history
  • Loading branch information
julieg18 authored Oct 12, 2021
1 parent 5912be9 commit c105507
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 3 deletions.
48 changes: 48 additions & 0 deletions src/components/Documentation/Markdown/ToggleProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -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<ITogglesContext>({})

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 (
<TogglesContext.Provider
value={{ addNewToggle, updateToggleInd, togglesData }}
>
{children}
</TogglesContext.Provider>
)
}
94 changes: 91 additions & 3 deletions src/components/Documentation/Markdown/index.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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>
Expand Down Expand Up @@ -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 (
<>
<input
id={inputId}
type="radio"
name={`toggle-${id}`}
onChange={onChange}
checked={checked}
/>
<label className={styles.tabHeading} htmlFor={inputId}>
{title}
</label>
{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 (
<div className={styles.toggle}>
{tabs.map((tab, i) => (
<ToggleTab
ind={i}
key={i}
title={tabsTitles[i]}
id={toggleId}
checked={
i === (togglesData[toggleId] ? togglesData[toggleId].checkedInd : 0)
}
onChange={(): void => updateToggleInd(toggleId, i)}
>
{tab}
</ToggleTab>
))}
</div>
)
}

const Tab: React.FC = ({ children }) => (
<div className={styles.tab}>{children}</div>
)

const renderAst = new rehypeReact({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createElement: React.createElement as any,
Expand All @@ -104,7 +190,9 @@ const renderAst = new rehypeReact({
abbr: Abbr,
a: Link,
card: Card,
cards: Cards
cards: Cards,
toggle: Toggle,
tab: Tab
}
}).Compiler

Expand All @@ -125,7 +213,7 @@ const Markdown: React.FC<IMarkdownProps> = ({
}) => {
return (
<Main prev={prev} next={next} tutorials={tutorials} githubLink={githubLink}>
{renderAst(htmlAst)}
<TogglesProvider>{renderAst(htmlAst)}</TogglesProvider>
</Main>
)
}
Expand Down
50 changes: 50 additions & 0 deletions src/components/Documentation/Markdown/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit c105507

Please sign in to comment.