Skip to content

Commit 0791d7a

Browse files
authored
Add icon components (#487)
This adds a script that can generate .gts components from the .svg icons in the public folder. This setup saves us a lot of manual work and it easily allows us to manage the icons we bundle.
1 parent c60e976 commit 0791d7a

File tree

7 files changed

+97
-7
lines changed

7 files changed

+97
-7
lines changed

.github/workflows/ci.yml

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ jobs:
2626
cache: npm
2727
- name: Install Dependencies
2828
run: npm ci
29+
- name: Build icons
30+
run: npm run build:icons
2931
- name: Lint
3032
run: npm run lint
3133
- name: Run Tests
@@ -44,6 +46,8 @@ jobs:
4446
cache: npm
4547
- name: Install Dependencies
4648
run: npm install --no-shrinkwrap
49+
- name: Build icons
50+
run: npm run build:icons
4751
- name: Run Tests
4852
run: npm run test:ember
4953

@@ -73,5 +77,7 @@ jobs:
7377
cache: npm
7478
- name: Install Dependencies
7579
run: npm ci
80+
- name: Build icons
81+
run: npm run build:icons
7682
- name: Run Tests
7783
run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/build-storybook.log
66
/.storybook/preview-head.html
77
/declarations/
8+
/addon/components/icons/
89

910
# dependencies
1011
/node_modules/

.npmignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# build scripts
2+
/lib/generate-icon-components.mjs
3+
14
# compiled output
25
/dist/
36
/tmp/

lib/generate-icon-components.mjs

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { pascalCase } from "change-case";
2+
import { existsSync } from "node:fs";
3+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
4+
import { basename, dirname, extname, join } from "node:path";
5+
import { fileURLToPath } from "node:url";
6+
7+
const __dirname = dirname(fileURLToPath(import.meta.url));
8+
9+
const ICON_FOLDER = join(__dirname, "..", "public", "icons");
10+
const COMPONENT_ICON_FOLDER = join(
11+
__dirname,
12+
"..",
13+
"addon",
14+
"components",
15+
"icons",
16+
);
17+
18+
const files = await readdir(ICON_FOLDER);
19+
const icons = files
20+
.filter((file) => extname(file) === ".svg")
21+
.map((svg) => basename(svg, ".svg"));
22+
23+
await prepareOutputDir();
24+
25+
const promises = icons.map((svg) => {
26+
return generateComponent(svg);
27+
});
28+
await Promise.all(promises);
29+
30+
async function generateComponent(iconName) {
31+
const componentName = pascalCase(iconName, {
32+
mergeAmbiguousCharacters: true,
33+
});
34+
35+
const iconContent = (await readFile(join(ICON_FOLDER, iconName + ".svg")))
36+
.toString()
37+
.replace(">", " ...attributes>"); // We assume the first closing bracket belongs to the svg element
38+
39+
const componentContent = `// THIS FILE IS GENERATED. ANY CHANGES TO THIS FILE WILL BE LOST.
40+
import type { TOC } from '@ember/component/template-only';
41+
42+
export interface ${componentName}IconSignature {
43+
Element: SVGSVGElement;
44+
}
45+
46+
export const ${componentName}Icon: TOC<${componentName}IconSignature> = <template>${iconContent}</template>;`;
47+
48+
await writeFile(
49+
join(COMPONENT_ICON_FOLDER, iconName + ".gts"),
50+
componentContent,
51+
);
52+
}
53+
54+
async function prepareOutputDir() {
55+
if (existsSync(COMPONENT_ICON_FOLDER)) {
56+
await rm(COMPONENT_ICON_FOLDER, { recursive: true });
57+
}
58+
await mkdir(COMPONENT_ICON_FOLDER);
59+
}

package-lock.json

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"test": "tests"
3333
},
3434
"scripts": {
35-
"build": "run-s icons build-ember build-storybook",
35+
"build": "run-s build-ember build-storybook",
36+
"build:icons": "concurrently \"node lib/generate-icon-components.mjs\" \"svg-symbols ./public/icons > ./public/appuniversum-symbolset.svg\"",
3637
"build:types": "glint -d",
3738
"lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
3839
"lint:css": "stylelint --allow-empty-input \"**/*.css\"",
@@ -45,20 +46,18 @@
4546
"lint:js": "eslint . --cache",
4647
"lint:js:fix": "eslint . --fix",
4748
"lint:types": "glint",
48-
"prepack": "glint -d",
49+
"prepack": "run-s build:icons build:types",
4950
"postpack": "rimraf declarations",
5051
"start": "run-p ember storybook",
51-
"ember": "ember serve",
52+
"ember": "npm run build:icons && ember serve",
5253
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
5354
"test:ember": "ember test",
5455
"test:ember-compatibility": "ember try:each",
55-
"icons": "svg-symbols ./public/icons > ./public/appuniversum-symbolset.svg",
56-
"prepare": "svg-symbols ./public/icons > ./public/appuniversum-symbolset.svg",
5756
"storybook": "start-storybook -p 6006",
5857
"build-storybook": "build-storybook",
59-
"build-ember": "ember build --environment=production",
58+
"build-ember": "npm run build:icons && ember build --environment=production",
6059
"build-ember-chromatic": "ember build --environment=chromatic",
61-
"prep-chromatic": "run-s icons build-ember-chromatic",
60+
"prep-chromatic": "run-s build:icons build-ember-chromatic",
6261
"release": "release-it",
6362
"chromatic": "chromatic --exit-zero-on-changes"
6463
},
@@ -135,6 +134,7 @@
135134
"@typescript-eslint/eslint-plugin": "^7.1.1",
136135
"@typescript-eslint/parser": "^7.1.1",
137136
"broccoli-asset-rev": "^3.0.0",
137+
"change-case": "^5.4.3",
138138
"chromatic": "^6.5.4",
139139
"concurrently": "^8.2.2",
140140
"ember-auto-import": "^2.7.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { module, test } from 'qunit';
2+
import { setupRenderingTest } from 'ember-qunit';
3+
import { render } from '@ember/test-helpers';
4+
import { AddIcon } from '@appuniversum/ember-appuniversum/components/icons/add';
5+
6+
module('Integration | Icon components', function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
test('the icon components accept attributes', async function (assert) {
10+
await render(<template><AddIcon data-test-icon /></template>);
11+
12+
assert.dom('[data-test-icon]').exists();
13+
});
14+
});

0 commit comments

Comments
 (0)