Skip to content

Commit

Permalink
docs: [BD-46] table of content for non-components (#2013)
Browse files Browse the repository at this point in the history
  • Loading branch information
monteri authored and viktorrusakov committed Aug 23, 2023
1 parent 5aa0bf1 commit 6692b5d
Show file tree
Hide file tree
Showing 16 changed files with 231 additions and 67 deletions.
3 changes: 2 additions & 1 deletion example/src/MyComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Button, Form, Icon, Bubble } from '@edx/paragon'; // eslint-disable-line
import { Button, Form, Icon, Bubble, Skeleton } from '@edx/paragon'; // eslint-disable-line
import { FavoriteBorder } from '@edx/paragon/icons'; // eslint-disable-line

const MyComponent = () => {
Expand Down Expand Up @@ -27,6 +27,7 @@ const MyComponent = () => {
</Form.Group>
<Button onClick={handleClick}>Submit</Button>
</Form>
<Skeleton />
</div>
);
};
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion www/.env.development
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
SEGMENT_KEY=''
FEATURE_ENABLE_AXE='true'
FEATURE_ENABLE_AXE=''
3 changes: 2 additions & 1 deletion www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"rehype-slug": "^4.0.1",
"sass": "^1.53.0",
"sass-loader": "12.6.0",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"slugify": "^1.6.5"
},
"keywords": [
"paragon",
Expand Down
132 changes: 132 additions & 0 deletions www/src/components/AutoToc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Sticky } from '~paragon-react';
import slugify from 'slugify';

interface IItems {
url?: string,
title?: string,
items?: Array<IItems>,
}

export interface IAutoToc {
className?: string,
tab?: string,
addAnchors?: boolean,
}

function createAnchor(slug: string): HTMLAnchorElement {
const anchor = document.createElement('a');
anchor.ariaHidden = 'true';
anchor.tabIndex = -1;
anchor.href = `#${slug}`;
const span = document.createElement('span');
span.className = 'pgn-doc__anchor';
span.innerText = '#';
anchor.appendChild(span);
return anchor;
}

function getNestedHeadingsData(headingsArray: NodeListOf<HTMLHeadElement>): IItems {
const result: IItems = { items: [] };
let parentHeadingLevel = 2;
headingsArray.forEach(heading => {
const headingLevel = parseInt(heading.tagName.slice(-1), 10);
const headingData = {
url: `#${heading.id}`,
title: heading.firstChild!.textContent!,
items: [],
};
if (!result.items!.length || headingLevel <= parentHeadingLevel) {
parentHeadingLevel = headingLevel;
result.items!.push(headingData);
} else {
const headingDepth = headingLevel - parentHeadingLevel;
let target = result.items![result.items!.length - 1];
for (let i = 1; i < headingDepth; i++) {
if (target?.items!.length) {
target = target.items[target.items.length - 1];
}
}
target.items!.push(headingData);
}
});
return result;
}

function AutoToc({ className, tab = '', addAnchors = true }: IAutoToc) {
const [active, setActive] = useState('');
const [headingsData, setHeadingsData] = useState<IItems>({ items: [] });
const observer = useRef<IntersectionObserver>();

useEffect(() => {
const handleObserver = (entries: IntersectionObserverEntry[]) => {
if (entries[0].intersectionRatio >= 0.5) {
setActive(entries[0].target.id);
}
};

observer.current = new IntersectionObserver(handleObserver, { rootMargin: '-50px 0px -80% 0px', threshold: 0.5 });
const elements = document.querySelectorAll<HTMLHeadElement>('main h2, main h3, main h4, main h5, main h6');
if (addAnchors) {
elements.forEach(el => {
if (el.textContent) {
el.classList.add('pgn-doc__heading');
const slug = slugify(el.textContent, { lower: true });
el.id = slug;
const anchor = createAnchor(slug);
el.appendChild(anchor);
}
});
}
const headings = getNestedHeadingsData(elements);
setHeadingsData(headings);
elements.forEach((elem) => observer.current?.observe(elem));

return () => observer.current?.disconnect();
}, [tab, addAnchors]);

const generateTree = (headings: { items?: Array<IItems> }) => (headings?.items?.length
? (
<ul className="pgn-doc__toc-list">
{headings.items.map(heading => (
<li key={heading.url}>
<a
href={heading.url}
className={classNames({ active: `#${active}` === heading.url })}
>
{heading.title}
</a>
{!!heading.items && generateTree(heading)}
</li>
))}
</ul>
) : null);

const tocTree = generateTree(headingsData);

return (
<Sticky
offset={6}
className={classNames('pgn-doc__toc', className)}
>
<p className="pgn-doc__toc-header">Contents</p>
{tocTree}
</Sticky>
);
}

AutoToc.propTypes = {
className: PropTypes.string,
tab: PropTypes.string,
addAnchors: PropTypes.bool,
};

AutoToc.defaultProps = {
className: undefined,
tab: undefined,
addAnchors: undefined,
};

export default AutoToc;
2 changes: 1 addition & 1 deletion www/src/components/IconsTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
flex-direction: column;
align-items: center;

h3 {
p {
margin-bottom: 0;
padding: 0 .25rem;
}
Expand Down
2 changes: 1 addition & 1 deletion www/src/components/IconsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function IconsTable({ iconNames }) {
className="pgn-doc__icons-table__preview-title"
onClick={() => copyToClipboard(currentIcon)}
>
<h3 className="rounded">{currentIcon}</h3>
<p className="rounded h3">{currentIcon}</p>
<Icon
key="ContentCopy"
src={IconComponents.ContentCopy}
Expand Down
11 changes: 10 additions & 1 deletion www/src/components/PageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Settings from './Settings';
import Toc from './Toc';
import { SettingsContext } from '../context/SettingsContext';
import LeaveFeedback from './LeaveFeedback';
import AutoToc from './AutoToc';

if (process.env.NODE_ENV === 'development') {
/* eslint-disable-next-line global-require */
Expand All @@ -37,6 +38,8 @@ export interface ILayout {
hideFooterComponentMenu: boolean,
isMdx: boolean,
tocData: Array<number>,
tab?: string,
isAutoToc?: boolean,
}

function Layout({
Expand All @@ -45,6 +48,8 @@ function Layout({
hideFooterComponentMenu,
isMdx,
tocData,
isAutoToc,
tab,
}: ILayout) {
const isMobile = useMediaQuery({ maxWidth: breakpoints.extraLarge.minWidth });
const { settings } = useContext(SettingsContext);
Expand Down Expand Up @@ -89,8 +94,9 @@ function Layout({
<Col
xl={2}
lg={3}
as={Toc}
as={isAutoToc ? AutoToc : Toc}
data={tocData}
tab={tab}
className="d-none d-lg-block"
/>
</Row>
Expand Down Expand Up @@ -169,13 +175,16 @@ Layout.propTypes = {
showMinimizedTitle: PropTypes.bool,
hideFooterComponentMenu: PropTypes.bool,
isMdx: PropTypes.bool,
tab: PropTypes.string,
};

Layout.defaultProps = {
tocData: {},
showMinimizedTitle: false,
hideFooterComponentMenu: false,
isMdx: false,
tab: undefined,
isAutoToc: false,
};

export default Layout;
30 changes: 16 additions & 14 deletions www/src/pages/foundations/colors.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import React, { useContext } from 'react';
import { graphql } from 'gatsby';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Container } from '~paragon-react';
import Color from 'color';
import { Container } from '~paragon-react';
import SEO from '../../components/SEO';
import MeasuredItem from '../../components/MeasuredItem';
import Layout from '../../components/PageLayout';
import { SettingsContext } from '../../context/SettingsContext';

const utilityClasses = {
bg: (color: string, level: number) => (level ? `bg-${color}-${level}` : `bg-${color}`),
Expand Down Expand Up @@ -102,7 +103,7 @@ const renderColorRamp = (themeName: string, unusedLevels: number[]) => (
key={`${themeName}`}
style={{ flexBasis: '24%', marginRight: '1%', marginBottom: '2rem' }}
>
<h2 className="h5">{themeName}</h2>
<p className="h5">{themeName}</p>
{levels.map(level => (
<Swatch
key={`$${themeName}-${level}`}
Expand All @@ -124,13 +125,14 @@ export interface IColorsPage {

// eslint-disable-next-line react/prop-types
export default function ColorsPage({ data }: IColorsPage) {
const { settings } = useContext(SettingsContext);
parseColors(data.allCssUtilityClasses.nodes); // eslint-disable-line react/prop-types

return (
<Layout>
<Container size="md" className="py-5">
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Colors" />
<Layout isAutoToc>
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Colors" />
<Container size={settings.containerWidth} className="py-5">
<h1>Colors</h1>
<div className="d-flex flex-wrap">
{colors
Expand All @@ -143,7 +145,7 @@ export default function ColorsPage({ data }: IColorsPage) {
marginBottom: '2rem',
}}
>
<h2 className="h5">accents</h2>
<p className="h5">accents</p>

<Swatch name="$accent-a" colorClassName="bg-accent-a" />
<Swatch name="$accent-b" colorClassName="bg-accent-b" />
Expand Down Expand Up @@ -351,9 +353,9 @@ export default function ColorsPage({ data }: IColorsPage) {
backgrounds.
</p>
<div className="d-flex rounded overflow-hidden mb-3">
<h4 className="mb-0 w-100">Lighter Text</h4>
<h4 className="mb-0 w-100">Regular Text</h4>
<h4 className="mb-0 w-100">Darker Text</h4>
<p className="mb-0 w-100 h4">Lighter Text</p>
<p className="mb-0 w-100 h4">Regular Text</p>
<p className="mb-0 w-100 h4">Darker Text</p>
</div>
<div className="d-flex">
{[500, 700, 900].map(level => (
Expand Down Expand Up @@ -381,13 +383,13 @@ export default function ColorsPage({ data }: IColorsPage) {
<div>
<div className="d-flex rounded overflow-hidden mb-3">
<div className="w-100">
<h4 className="mb-0">Default State</h4>
<p className="mb-0 h4">Default State</p>
</div>
<div className="w-100">
<h4 className="mb-0">Hover State</h4>
<p className="mb-0 h4">Hover State</p>
</div>
<div className="w-100">
<h4 className="mb-0">Active State</h4>
<p className="mb-0 h4">Active State</p>
</div>
</div>
{colors.map(({ themeName }) => {
Expand Down
23 changes: 13 additions & 10 deletions www/src/pages/foundations/elevation.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';
import PropTypes from 'prop-types';
import {
Container,
Button,
Form,
Container,
Input,
Toast,
Icon,
Expand All @@ -12,6 +12,7 @@ import {
import { Close, WbSunny, DoDisturb } from '~paragon-icons';
import SEO from '../../components/SEO';
import Layout from '../../components/PageLayout';
import { SettingsContext } from '../../context/SettingsContext';

const boxShadowSides = ['down', 'up', 'right', 'left', 'centered'];
const boxShadowLevels = [1, 2, 3, 4, 5];
Expand Down Expand Up @@ -268,23 +269,25 @@ function BoxShadowGenerator() {
}

export default function ElevationPage() {
const { settings } = useContext(SettingsContext);

const levelTitle = boxShadowLevels.map(level => (
<h3 key={level} className="pgn-doc__box-shadow-level-title">
<p key={level} className="pgn-doc__box-shadow-level-title h3">
Level {level}
</h3>
</p>
));

const sideTitle = boxShadowSides.map(side => (
<h3 key={side} className="pgn-doc__box-shadow-side-title">
<p key={side} className="pgn-doc__box-shadow-side-title h3">
{side.charAt(0).toUpperCase() + side.substring(1)}
</h3>
</p>
));

return (
<Layout>
<Container className="py-5" size="md">
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Elevation" />
<Layout isAutoToc>
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Elevation" />
<Container size={settings.containerWidth} className="py-5">
<h1 className="mb-3">Elevation & Shadow</h1>
<p className="mb-5">
You can quickly add a <code>box-shadow</code> with the Clickable Box-Shadow Grid.
Expand Down
Loading

0 comments on commit 6692b5d

Please sign in to comment.