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

External icon libraries & token generation #18

Merged
merged 9 commits into from
Jul 31, 2023
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
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ src/react/index.ts
node_modules
package-lock.json
tsconfig.json
src/styles/exports/generated.css

30 changes: 17 additions & 13 deletions docs/assets/plugins/code-block/code-block.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
const version = sessionStorage.getItem('sl-version');

html = html
.replace(/@teamshares\/shoelace/g, `https://cdn.skypack.dev/@teamshares/shoelace@${version}`)
.replace(/@teamshares\/shoelace/g, `https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}`)
.replace(/from 'react'/g, `from 'https://cdn.skypack.dev/react@${reactVersion}'`)
.replace(/from "react"/g, `from "https://cdn.skypack.dev/react@${reactVersion}"`);

Expand Down Expand Up @@ -326,7 +326,6 @@

if (button?.classList.contains('code-block__button--codepen')) {
const codeBlock = button.closest('.code-block');
// const htmlExample = codeBlock.querySelector('.code-block__source--html > pre > code')?.textContent;
const reactExample = codeBlock.querySelector('.code-block__source--react > pre > code')?.textContent;
const slimExample = codeBlock.querySelector('.code-block__source--slim > pre > code')?.textContent;
const isReact = flavor === 'react' && typeof reactExample === 'string';
Expand All @@ -345,10 +344,10 @@

// HTML templates
if (!isReact) {
htmlTemplate =
`<script type="module" src="https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/shoelace.js"></script>\n` +
`\n${slimExample}`;
jsTemplate = '';
htmlTemplate = `${slimExample}`;
jsTemplate =
`import { registerExternalLibraries } from 'https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/utilities/icon-library.js';\n` +
`registerExternalLibraries();`;
}

// React templates
Expand All @@ -357,41 +356,46 @@
jsTemplate =
`import React from 'https://cdn.skypack.dev/react@${reactVersion}';\n` +
`import ReactDOM from 'https://cdn.skypack.dev/react-dom@${reactVersion}';\n` +
`import { setBasePath } from 'https://cdn.skypack.dev/@teamshares/shoelace@${version}/dist/utilities/base-path';\n` +
`import { setBasePath } from 'https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/utilities/base-path';\n` +
`\n` +
`// Set the base path for Shoelace assets\n` +
`setBasePath('https://cdn.skypack.dev/@teamshares/shoelace@${version}/dist/')\n` +
`setBasePath('https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/')\n` +
`\n${convertModuleLinks(reactExample)}\n` +
`\n` +
`ReactDOM.render(<App />, document.getElementById('root'));`;
}

// CSS templates
// TODO: Once we have a our Tailwind classes loaded in the docs site, we should also load them here
cssTemplate =
`@import 'https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/themes/${
isDark ? 'dark' : 'light'
}.css';\n` +
`@import 'https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/styles/index.css';\n` +
'\n' +
'body {\n' +
' font: 16px sans-serif;\n' +
' background-color: var(--sl-color-neutral-0);\n' +
' color: var(--sl-color-neutral-900);\n' +
' padding: 1rem;\n' +
'}';

const headTemplate =
`<meta name='viewport' content='width=device-width'>\n` +
`<script type='module' src='https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/shoelace.js'></script>`;

// Docs: https://blog.codepen.io/documentation/prefill/
const data = {
title: '',
description: '',
tags: ['shoelace', 'web components'],
tags: ['shoelace', 'web components', 'teamshares'],
editors,
head: `<meta name="viewport" content="width=device-width">`,
head: headTemplate,
html_classes: `sl-theme-${isDark ? 'dark' : 'light'}`,
html_pre_processor: isReact ? 'none' : 'slim',
css_external: `https://os.teamshares.com/assets/application-cd5dbca3027c43e480efd5a0efc734bb30fd761b.css`,
css_external: '', // Note that we are importing CSS via the template above
css_pre_processor: 'scss',
js_module: true,
js_external: `https://cdn.jsdelivr.net/npm/@teamshares/shoelace@${version}/dist/shoelace.js`, // This doesn't appear to work, perhaps because it lacks type=module (even though module is true below)
js_external: '', // Note that the shoelace module include needs to be in the <head> block rather than here
js_pre_processor: isReact ? 'babel' : 'none',
html: htmlTemplate,
css: cssTemplate,
Expand Down
34 changes: 2 additions & 32 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
<link rel="icon" href="assets/images/ts-alee.svg" type="image/x-icon" />
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/ts-touch-icon.png" />

<!-- Import Shoelace -->
<link rel="stylesheet" href="dist/themes/light.css" />
<link rel="stylesheet" href="dist/themes/dark.css" />
<link rel="stylesheet" href="dist/styles/index.css" />
Expand All @@ -50,37 +49,8 @@

<script type="module" src="/dist/shoelace-autoloader.js"></script>
<script type="module">
import { registerIconLibrary } from '/dist/utilities/icon-library.js';

registerIconLibrary('fa-free', {
resolver: name => {
const filename = name.replace(/^fa[rbs]-/, '');
let folder = 'regular';
if (name.substring(0, 4) === 'fas-') folder = 'solid';
if (name.substring(0, 4) === 'fab-') folder = 'brands';
return `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.3.0/svgs/${folder}/${filename}.svg`;
},
mutator: svg => svg.setAttribute('fill', 'currentColor')
});

registerIconLibrary('fa', {
resolver: name => {
const filename = name.replace(/^fa([rsltdb]|(ss))-/, '');
const sub = name.substring(0, 4);
const folderHash = {
'fas-': 'solid',
'fal-': 'light',
'fat-': 'thin',
'fad-': 'duotone',
'fab-': 'brands'
};
const folder = folderHash[sub] || 'regular';
/* Note: The token refers to the Teamshares Font Awesome Kit */
/* See https://fontawesome.com/kits/44da2a9d09/setup */
return `https://ka-p.fontawesome.com/releases/v6.4.0/svgs/${folder}/${filename}.svg?token=44da2a9d09`;
},
mutator: svg => svg.setAttribute('fill', 'currentColor')
});
import { registerExternalLibraries } from '/dist/utilities/icon-library.js';
registerExternalLibraries();
</script>
</head>
<body data-shoelace="/dist/">
Expand Down
8 changes: 8 additions & 0 deletions docs/teamshares/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.2.2

- Utilities now export a method to automatically register FontAwesome icon libraries
- Slim template formatting for CodePen examples
- Tokens and overrides included in CodePen examples
- New export of tailwind theme to be consumed by shared-ui as part of config
- CSS tokens generated automatically from the tailwind.js config

## 1.2.1

- Fix focus issue on inputs caused by Tailwind
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@teamshares/shoelace",
"description": "The Teamshares flavor of a forward-thinking library of web components.",
"version": "1.2.1",
"version": "1.2.2",
"upstreamVersion": "2.4.0",
"homepage": "https://github.com/teamshares/shoelace",
"author": "Cory LaViska",
Expand All @@ -23,7 +23,8 @@
"./dist/react/*": "./dist/react/*",
"./dist/translations/*": "./dist/translations/*",
"./dist/styles": "./dist/styles/index.css",
"./dist/styles/*": "./dist/styles/*"
"./dist/styles/*": "./dist/styles/*",
"./dist/tokens": "./dist/styles/tokens.json"
},
"files": [
"dist"
Expand Down
1 change: 1 addition & 0 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fs.mkdirSync(outdir, { recursive: true });
execSync(`node scripts/make-search.js --outdir "${outdir}"`, { stdio: 'inherit' });
execSync(`node scripts/make-react.js --outdir "${outdir}"`, { stdio: 'inherit' });
execSync(`node scripts/make-web-types.js --outdir "${outdir}"`, { stdio: 'inherit' });
execSync(`node scripts/make-tokens.js --outdir "${outdir}"`, { stdio: 'inherit' });
execSync(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' });
execSync(`node scripts/make-styles.js --outdir "${outdir}"`, { stdio: 'inherit' });
execSync(`node scripts/make-icons.js --outdir "${outdir}"`, { stdio: 'inherit' });
Expand Down
11 changes: 9 additions & 2 deletions scripts/make-styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { mkdirSync } from 'fs';
import { globbySync } from 'globby';
import path from 'path';
import prettier from 'prettier';
import stripComments from 'strip-css-comments';

const { outdir } = commandLineArgs({ name: 'outdir', type: String });
const files = globbySync('./src/styles/exports/**/*.css');
Expand All @@ -23,7 +22,7 @@ try {
files.forEach(file => {
let source = fs.readFileSync(file, 'utf8');

const css = prettier.format(stripComments(source), {
const css = prettier.format(source, {
parser: 'css'
});

Expand All @@ -35,3 +34,11 @@ try {
console.error(chalk.red('Error generating export stylesheets!'));
console.error(err);
}

// Copy the tokens.json over
try {
const tokenDistPath = path.join(outdir, 'styles', 'tokens.json');
fs.copyFileSync('./src/styles/tokens.json', tokenDistPath);
} catch (err) {
console.error(chalk.red('Error writing tokens JSON file:'), err);
}
111 changes: 111 additions & 0 deletions scripts/make-tokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* Read from the JSON source of truth and write a css file using the template */

import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import prettier from 'prettier';
import tokens from './../src/styles/tokens.json' assert { type: 'json' };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL import + assert`

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a new / experimental Node thing. You get a warning in the console, but it works.


const colorFamilies = ['blue', 'gray', 'red', 'green', 'yellow', 'teal', 'purple', 'fuchsia'];
const colors = tokens.colors;
const variablePrefix = '--ts-color-';

function hexToRgb(hex) {
const hexValue = hex.replace('#', '');
const r = parseInt(hexValue.substring(0, 2), 16);
const g = parseInt(hexValue.substring(2, 4), 16);
const b = parseInt(hexValue.substring(4, 6), 16);
return { r, g, b };
}

function generateColorVariables(colorFamily) {
let css = ` /** ${colorFamily} */\n`;
let hexCss = ``;
let rgbCss = ``;
for (const weight in colors[colorFamily]) {
const colorValue = colors[colorFamily][weight];
hexCss += ` ${variablePrefix}${colorFamily}-${weight}: ${colorValue};\n`;
const rgbValue = hexToRgb(colorValue);
rgbCss += ` ${variablePrefix}${colorFamily}-${weight}-rgb: ${rgbValue.r}, ${rgbValue.g}, ${rgbValue.b};\n`;
}
css += hexCss + rgbCss;
return css;
}

function generateSemanticVariants(semanticFamily, colorFamily) {
let css = ` /** ${semanticFamily} => ${colorFamily} */\n`;
for (const weight in colors[colorFamily]) {
css += ` ${variablePrefix}${semanticFamily}-${weight}: var(${variablePrefix}${colorFamily}-${weight});\n`;
}
return css;
}

function generateShoelaceColorOverride(colorFamily) {
let css = ` /** ${colorFamily} override */\n`;
// Use the weights from blue for all of these
for (const weight in colors['blue']) {
css += ` --sl-color-${colorFamily}-${weight}: var(${variablePrefix}${colorFamily}-${weight});\n`;
}
return css;
}

console.log('Generating tokens CSS');

let cssContent =
`/** *********************************************************************\n` +
`/** Tokens generated from /src/styles/tokens.json\n` +
`/** Do not edit this file directly. Make changes in the json\n` +
`/** and then run 'npm run build'\n` +
`/** ********************************************************************* **/\n` +
`\n` +
`:host,\n:root,\n.sl-theme-light {\n` +
`\n\n /***** Generated colors **/\n\n`;

for (const colorFamily of colorFamilies) {
if (colors[colorFamily]) {
cssContent += generateColorVariables(colorFamily);
}
}

cssContent += `\n /***** Semantic color variants **/\n\n`;

const semanticHash = {
primary: 'blue',
success: 'green',
warning: 'yellow',
danger: 'red',
neutral: 'gray'
};
for (const semanticFamily in semanticHash) {
cssContent += generateSemanticVariants(semanticFamily, semanticHash[semanticFamily]);
}

cssContent += `\n /***** Shoelace color overrides **/\n\n`;

const shoelaceColors = colorFamilies.concat(Object.keys(semanticHash));
for (const shoelaceColor of shoelaceColors) {
cssContent += generateShoelaceColorOverride(shoelaceColor);
}

cssContent += `\n /***** Other generated tokens **/\n\n`;

const font = tokens.fontFamily;
cssContent +=
` /**** Generated fonts **/\n` +
` --ts-font-sans: ${font.sans};\n` +
` --ts-font-serif: ${font.serif};\n` +
` --ts-font-mono: ${font.mono};\n` +
` --ts-font-body: var(--ts-font-sans);\n` +
` --ts-font-display: var(--ts-font-serif);\n`;

cssContent += `}\n`;

cssContent = prettier.format(cssContent, { parser: 'css' });

// Write the CSS file
try {
const fileName = path.join('./src/styles/exports', 'generated.css');
fs.writeFileSync(fileName, cssContent, 'utf8');
} catch (err) {
console.error(chalk.red('Error writing CSS file:'), err);
}
35 changes: 35 additions & 0 deletions src/components/icon/external.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { registerIconLibrary } from './library';

/* Teamshares-specific icon libraries */
export function registerExternalLibraries() {
registerIconLibrary('fa-free', {
resolver: name => {
const filename = name.replace(/^fa[rbs]-/, '');
let folder = 'regular';
if (name.startsWith('fas-')) folder = 'solid';
if (name.startsWith('fab-')) folder = 'brands';
return `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.3.0/svgs/${folder}/${filename}.svg`;
},
mutator: svg => svg.setAttribute('fill', 'currentColor')
});

registerIconLibrary('fa', {
resolver: name => {
const filename = name.replace(/^fa([rsltdb]|(ss))-/, '');
const sub = name.substring(0, 4);
const folderHash = {
'fas-': 'solid',
'fal-': 'light',
'fat-': 'thin',
'fad-': 'duotone',
'fab-': 'brands'
};
const folder: unknown = folderHash[sub as keyof typeof folderHash] || 'regular';
/* Note: The token refers to the Teamshares Font Awesome Kit */
/* See https://fontawesome.com/kits/44da2a9d09/setup */
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
return `https://ka-p.fontawesome.com/releases/v6.4.0/svgs/${folder}/${filename}.svg?token=44da2a9d09`;
},
mutator: svg => svg.setAttribute('fill', 'currentColor')
});
}
2 changes: 2 additions & 0 deletions src/components/icon/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ export function registerIconLibrary(
export function unregisterIconLibrary(name: string) {
registry = registry.filter(lib => lib.name !== name);
}

export { registerExternalLibraries } from './external';
Loading