Skip to content

Commit

Permalink
docs: icon sets (#1844)
Browse files Browse the repository at this point in the history
* docs: stubbed icons page

* docs: icons prose

* chore: tslib

* docs: max out the card

* docs: icon page layout
  • Loading branch information
bennypowers authored Sep 16, 2024
1 parent e9ceed3 commit 6f3d7b8
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 31 deletions.
12 changes: 9 additions & 3 deletions docs/_includes/layouts/pages/basic.njk
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@ layout: layouts/base.njk
{% include 'partials/component/masthead.njk' %}
{% include 'partials/component/sidenav.njk' %}

{%- if hasToc -%}
{%- set tags = tocTags or ['h2'] -%}
{%- set table = content | toc(tags=tocTags) -%}
{%- endif -%}

<rh-surface id="main"
role="main"
color-palette="lightest">
<article {% if hasToc and (content | toc).length > 0 %}class="has-toc"{% endif %}>
<article {{ "class=has-toc" if table.length > 0 }}>

{% include 'partials/component/header.njk' %}
{% if hasToc and (content | toc).length > 0 %}

{% if table.length > 0 %}
<uxdot-toc summary="On this page">
{{ content | toc(tags=tocTags or ['h2']) | safe }}
{{ table | safe }}
</uxdot-toc>
{% endif %}

Expand Down
1 change: 1 addition & 0 deletions docs/_includes/partials/component/sidenav.njk
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

{% endfor %}

<uxdot-sidenav-item href="/icons/">Icons</uxdot-sidenav-item>
<uxdot-sidenav-item href="/design-code-status/">Design/code status</uxdot-sidenav-item>
<uxdot-sidenav-item href="/release-notes/">Release notes</uxdot-sidenav-item>
<uxdot-sidenav-item href="/support/">Get support</uxdot-sidenav-item>
Expand Down
40 changes: 29 additions & 11 deletions docs/_plugins/table-of-contents.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
const ignoreAttribute = 'data-toc-exclude';

const defaults = {
tags: ['h2', 'h3', 'h4'],
tags: ['h2'],
/** @type{string[]} */
ignoredElements: [],
};
Expand Down Expand Up @@ -83,25 +83,35 @@ class Toc {
* @param {Options} options
*/
constructor(htmlstring = '', options) {
const { queryAll, hasAttribute, isElementNode } = options.Tools;
const { parse } = options.Parse5;
this.options = { ...defaults, ...options };
this.root = new Item(options);
this.html = htmlstring;
this.options = options;
this.root = new Item(this.options);
this.root.parent = this.root;

const document = parse(htmlstring);
this.parse();
}

parse() {
const { parse } = this.options.Parse5;
const { queryAll, hasAttribute, isElementNode } = this.options.Tools;

const document = parse(this.html);

const tagSet =
new Set(this.options.tags)
.difference(new Set(this.options.ignoredElements));

const headings = queryAll(document, node => isElementNode(node)
&& this.options.tags.includes(node.tagName)
&& !this.options.ignoredElements.includes(node.tagName)
const headings = queryAll(document, node =>
isElementNode(node)
&& tagSet.has(node.tagName)
&& hasAttribute(node, 'id')
&& !hasAttribute(node, ignoreAttribute));

let previous = this.root;

for (const heading of headings) {
if (isElementNode(heading)) {
const current = new Item(options, heading);
const current = new Item(this.options, heading);
const parent = getParent(previous, current);
current.parent = parent;
parent.children.push(current);
Expand All @@ -126,7 +136,15 @@ module.exports = {
async function(content, opts) {
const Parse5 = await import('parse5');
const Tools = await import('@parse5/tools');
const toc = new Toc(content, { ...options, ...opts, Parse5, Tools });
const toc = new Toc(content, {
...defaults,
...options,
...opts,
tags: opts?.tags || options?.tags,
Parse5,
Tools,
page: this.page,
});
const html = toc.serialize();
return html;
});
Expand Down
73 changes: 73 additions & 0 deletions docs/icons/icons.11ty.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// for editor highlighting
const html = String.raw;

module.exports = class IconsPage {
async data() {
return {
permalink: '/icons/index.html',
layout: 'layouts/pages/basic.njk',
title: 'Icons',
hasToc: true,
icons: await import('@rhds/icons'),
};
}

render({ icons }) {
return html`
<script type="module" src="icons.js" data-helmet></script>
<style data-helmet>
.icon-set {
padding: 0;
list-style-type: none;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: var(--rh-space-sm);
}
</style>
<p>Click icons to copy their HTML elements</p>
<section>
<h2 id="social-icons">Social Icons</h2>
<p>These icons represent or link to social media companies</p>
<ul class="icon-set">${this.renderIcons('social', icons)}</ul>
</section>
<section>
<h2 id="standard-icons">Standard Icons</h2>
<p>Use these icons as graphics, and at large sizes</p>
<rh-alert state="warning">Avoid using these for UI elements like buttons</rh-alert>
<ul class="icon-set">${this.renderIcons('standard', icons)}</ul>
</section>
<section>
<h2 id="ui-icons">UI Icons</h2>
<p>Use these icons in UI controls like buttons and form fields</p>
<ul class="icon-set">${this.renderIcons('ui', icons)}</ul>
</section>
<section>
<h2 id="micron-icons">Microns</h2>
<p>Microns is a funny word</p>
<ul class="icon-set">${this.renderIcons('microns', icons)}</ul>
</section>
`;
}

renderIcons(set, icons) {
return Array.from(icons[set].keys(), icon => this.renderIcon({ icon, set })).join('');
}

renderIcon({ set, icon }) {
return html`
<li>
<rh-button accessible-label="Copy icon HTML for ${set} ${icon}"
icon="${icon}"
icon-set="${set}"
variant="tertiary">${icon}</rh-button>
</li>
`;
}
};

15 changes: 15 additions & 0 deletions docs/icons/icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import '@rhds/elements/rh-icon/rh-icon.js';
import '@rhds/elements/rh-tooltip/rh-tooltip.js';
import { RhButton } from '@rhds/elements/rh-button/rh-button.js';
import { RhAlert } from '@rhds/elements/rh-alert/rh-alert.js';

document.addEventListener('click', async function(event) {
const { icon, iconSet } = event.target;
if (event.target instanceof RhButton && icon && iconSet) {
const html = /* html*/`<rh-icon set="${iconSet}" icon="${icon}"></rh-icon>`;
await navigator.clipboard.writeText(html);
// TODO: syntax highlight this
const message = html;
await RhAlert.toast({ heading: 'Copied', message });
}
});
1 change: 1 addition & 0 deletions eleventy.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy('docs/styles/**/*');
eleventyConfig.addPassthroughCopy('docs/patterns/**/*.css');
eleventyConfig.addPassthroughCopy('docs/theming/**/*.css');
eleventyConfig.addPassthroughCopy('docs/icons/**/*.{css,js}');


if (isLocal) {
Expand Down
40 changes: 23 additions & 17 deletions scripts/environment.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import { join } from 'node:path';
import { readdir, stat } from 'node:fs/promises';
import { readdir } from 'node:fs/promises';

let iconSetsMap;

/**
* create a javascript map containing icon names
*/
export async function getIconSetsMap() {
if (!iconSetsMap) {
// because the icon package exports DOM nodes, but this file runs in nodejs
await import('@patternfly/pfe-core/ssr-shims.js');
const { standard, social, ui, microns } = await import('@rhds/icons');
iconSetsMap = new Map([
['social', [...social.keys()]],
['standard', [...standard.keys()]],
['ui', [...ui.keys()]],
['microns', [...microns.keys()]],
]);
}
return iconSetsMap;
}

/**
* create a javascript module containing element and icon names
*/
export async function makeDemoEnv() {
const iconsDir = join(process.cwd(), 'node_modules', '@rhds', 'icons');
const dirContents = await readdir(iconsDir);
const dirNamesOrNulls = await Promise.all(dirContents.map(async x => {
const stats = await stat(join(iconsDir, x));
if (!x.startsWith('.') && stats.isDirectory()) {
return x;
} else {
return null;
}
}));
const iconSetNames = dirNamesOrNulls.filter(x => x != null);
const iconSets = await Promise.all(iconSetNames.reverse().map(async set => {
const files = await readdir(join(iconsDir, set));
return [set, [...new Set(files.map(x => x.replace(/\..*$/, '')))]];
}));
iconSetsMap ??= await getIconSetsMap();
const javascript = String.raw; // for editor highlighting
const allElements = (await readdir(join(process.cwd(), 'elements')))
.filter(path => !path.includes('.'));
return javascript`
export const elements = ${JSON.stringify(allElements)};
export const iconSets = new Map(${JSON.stringify(iconSets)});`;
export const iconSets = new Map(${JSON.stringify(Object.fromEntries(iconSetsMap.entries()))});`;
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"es2015.iterable",
"es2020",
"es2022",
"ESNext.Collection",
"ESNext.Disposable",
"es5",
"es6",
Expand Down

0 comments on commit 6f3d7b8

Please sign in to comment.