Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add markdown tabs #2914

Merged
merged 3 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}