Skip to content

Commit

Permalink
feat: table of content for non-components
Browse files Browse the repository at this point in the history
  • Loading branch information
monteri committed Feb 13, 2023
1 parent d1f96b4 commit c29bc55
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 60 deletions.
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=''
1 change: 1 addition & 0 deletions www/package-lock.json

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

1 change: 1 addition & 0 deletions www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"rehype-autolink-headings": "^5.1.0",
"rehype-slug": "^4.0.1",
"sass": "^1.53.0",
"slugify": "^1.6.5",
"uuid": "^8.3.2"
},
"keywords": [
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;
55 changes: 55 additions & 0 deletions www/src/components/ContentWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Container, Row, Col,
} from '~paragon-react';
import AutoToc from './AutoToc';

export interface IContentWrapper {
children: React.ReactNode,
tab?: string,
addAnchors?: boolean,
}

function ContentWrapper({
children, tab, addAnchors,
}: IContentWrapper) {
return (
<Container fluid size={undefined}>
<Row className="flex-xl-nowrap">
<Col className="d-none d-xl-block" xl={2} />
<Col
xl={8}
lg={9}
md={12}
as="main"
className="flex-grow-1"
>
{children}
</Col>
<Col
xl={2}
lg={3}
as={AutoToc}
tab={tab}
addAnchors={addAnchors}
className="d-none d-lg-block"
/>
</Row>
</Container>
);
}

ContentWrapper.propTypes = {
children: PropTypes.node,
addAnchors: PropTypes.bool,
tab: PropTypes.string,
};

ContentWrapper.defaultProps = {
children: undefined,
addAnchors: undefined,
tab: undefined,
};

export default ContentWrapper;
2 changes: 1 addition & 1 deletion www/src/components/IconsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function IconsTable() {
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
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
26 changes: 13 additions & 13 deletions www/src/pages/foundations/colors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React 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 SEO from '../../components/SEO';
import MeasuredItem from '../../components/MeasuredItem';
import Layout from '../../components/PageLayout';
import ContentWrapper from '../../components/ContentWrapper';

const utilityClasses = {
bg: (color: string, level: number) => (level ? `bg-${color}-${level}` : `bg-${color}`),
Expand Down Expand Up @@ -102,7 +102,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 @@ -128,9 +128,9 @@ export default function ColorsPage({ data }: IColorsPage) {

return (
<Layout>
<Container size="md" className="py-5">
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Colors" />
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Colors" />
<ContentWrapper addAnchors>
<h1>Colors</h1>
<div className="d-flex flex-wrap">
{colors
Expand All @@ -143,7 +143,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 +351,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 +381,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 Expand Up @@ -437,7 +437,7 @@ export default function ColorsPage({ data }: IColorsPage) {
);
})}
</div>
</Container>
</ContentWrapper>
</Layout>
);
}
Expand Down
19 changes: 10 additions & 9 deletions www/src/pages/foundations/elevation.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {
Button, Form, Container, Input, Toast, Icon, IconButtonWithTooltip,
Button, Form, Input, Toast, Icon, IconButtonWithTooltip,
} from '~paragon-react';
import { Close, WbSunny, DoDisturb } from '~paragon-icons';
import SEO from '../../components/SEO';
import Layout from '../../components/PageLayout';
import ContentWrapper from '../../components/ContentWrapper';

const boxShadowSides = ['down', 'up', 'right', 'left', 'centered'];
const boxShadowLevels = [1, 2, 3, 4, 5];
Expand Down Expand Up @@ -263,22 +264,22 @@ function BoxShadowGenerator() {

export default function ElevationPage() {
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" />
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Elevation" />
<ContentWrapper addAnchors>
<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 Expand Up @@ -386,7 +387,7 @@ export default function ElevationPage() {
and so does this online tool. Use the Add New Layer button to save the current line and set up a new one.
</p>
<BoxShadowGenerator />
</Container>
</ContentWrapper>
</Layout>
);
}
11 changes: 6 additions & 5 deletions www/src/pages/foundations/responsive.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DataTable, Container, breakpoints } from '~paragon-react';
import { DataTable, breakpoints } from '~paragon-react';
import SEO from '../../components/SEO';
import Layout from '../../components/PageLayout';
import CodeBlock from '../../components/CodeBlock';
import ContentWrapper from '../../components/ContentWrapper';

const BREAKPOINT_DESCRIPTIONS = {
extraSmall: { name: 'Extra small', identifier: 'xs' },
Expand Down Expand Up @@ -37,9 +38,9 @@ function Responsive() {

return (
<Layout>
<Container size="md" className="py-5">
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Responsive" />
{/* eslint-disable-next-line react/jsx-pascal-case */}
<SEO title="Responsive" />
<ContentWrapper addAnchors>
<h1>Responsive</h1>
<h2>Available breakpoints</h2>
<p>
Expand Down Expand Up @@ -74,7 +75,7 @@ function Responsive() {
<CodeBlock className="language-scss">
{'@include media-breakpoint-up(map-get($grid-breakpoints, \'lg\')) { // styles here }'}
</CodeBlock>
</Container>
</ContentWrapper>
</Layout>
);
}
Expand Down
Loading

0 comments on commit c29bc55

Please sign in to comment.