diff --git a/.changeset/three-pianos-call.md b/.changeset/three-pianos-call.md new file mode 100644 index 000000000..bdccfbfea --- /dev/null +++ b/.changeset/three-pianos-call.md @@ -0,0 +1,5 @@ +--- +'@openproject/octicons': patch +--- + +feat: change icon names & selectors diff --git a/.changeset/tidy-cycles-perform.md b/.changeset/tidy-cycles-perform.md new file mode 100644 index 000000000..3a0b9e2a9 --- /dev/null +++ b/.changeset/tidy-cycles-perform.md @@ -0,0 +1,5 @@ +--- +'@openproject/octicons': patch +--- + +feat: add rendering function outside of Angular diff --git a/lib/octicons_angular/script/build.js b/lib/octicons_angular/script/build.js index 433f4c70b..95581e4b6 100755 --- a/lib/octicons_angular/script/build.js +++ b/lib/octicons_angular/script/build.js @@ -51,7 +51,7 @@ const icons = Object.entries(octicons) export const ${svgDataExportName} = ${generate(svgData).code}; @Component({ - selector: 'svg[op-octicon-${key}]', + selector: 'svg[${key}-icon]', standalone: true, imports: [NgIf, NgFor], template: \` @@ -81,7 +81,8 @@ async function writeIconExport(file) { const code = `${GENERATED_HEADER} import { Component } from '@angular/core'; import { NgFor, NgIf } from '@angular/common'; -import { OpOcticonComponentBase, SVGData } from '../octicon-component-base'; +import { OpOcticonComponentBase } from '../octicon-component-base'; +import { SVGData } from '../helpers'; ${icons.map(({code}) => code).join('\n')} ` diff --git a/lib/octicons_angular/src/helpers.ts b/lib/octicons_angular/src/helpers.ts index 339f2adab..8d3a3851f 100644 --- a/lib/octicons_angular/src/helpers.ts +++ b/lib/octicons_angular/src/helpers.ts @@ -1,5 +1,34 @@ +export type SVGSize = 'small'|'medium'|'large'; + +export interface SVGData { + [key: string]: { + width: number, + paths: string[], + }; +}; + +export const sizeMap = { + small: 16, + medium: 32, + large: 64 +}; + export function closestNaturalHeight(naturalHeights:string[], height:number) { return naturalHeights .map(naturalHeight => parseInt(naturalHeight, 10)) .reduce((acc, naturalHeight) => (naturalHeight <= height ? naturalHeight : acc), parseInt(naturalHeights[0], 10)) } + +export function toDOMString(data:SVGData, size:SVGSize = 'medium', extraAttributes:Record = {}) { + const height = sizeMap[size]; + const naturalHeight = closestNaturalHeight(Object.keys(data), height) + const { width, paths } = data[naturalHeight.toString()]; + const elWidth = height * (width / naturalHeight); + return ` `${total} ${attr}="${extraAttributes[attr]}"`, '')} + > + ${paths.map(p => ``)} + ` +} diff --git a/lib/octicons_angular/src/octicon-component-base.ts b/lib/octicons_angular/src/octicon-component-base.ts index a8b38cc70..d996adc3a 100644 --- a/lib/octicons_angular/src/octicon-component-base.ts +++ b/lib/octicons_angular/src/octicon-component-base.ts @@ -4,20 +4,13 @@ import { HostBinding } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; -import { closestNaturalHeight } from './helpers'; - -export interface SVGData { - [key: string]: { - width: number, - paths: string[], - }; -}; +import { closestNaturalHeight, SVGData, SVGSize, sizeMap } from './helpers'; @Directive({}) export class OpOcticonComponentBase { @Input() className = ''; @Input() fill = 'currentColor'; - @Input() size: 'small'|'medium'|'large'= 'medium'; + @Input() size:SVGSize = 'medium'; @Input() verticalAlign = 'text-bottom'; @Input() title = ''; @@ -52,11 +45,6 @@ export class OpOcticonComponentBase { @HostBinding('attr.height') get height() { - const sizeMap = { - small: 16, - medium: 32, - large: 64 - } return sizeMap[this.size]; } diff --git a/lib/octicons_angular/src/public-api.spec.ts b/lib/octicons_angular/src/public-api.spec.ts index cb8b86458..e722b4a19 100644 --- a/lib/octicons_angular/src/public-api.spec.ts +++ b/lib/octicons_angular/src/public-api.spec.ts @@ -6,6 +6,8 @@ import { opAddIconData, LogIconComponent, logIconData, + + toDOMString, } from './public-api'; describe('Github native icon', () => { @@ -28,19 +30,19 @@ describe('Github native icon', () => { it('should render the svg', () => { const iconElement: HTMLElement = fixture.nativeElement; - expect(iconElement.children[0].tagName.toLowerCase()).toEqual("path"); + expect(iconElement.children[0].tagName.toLowerCase()).toEqual('path'); expect(iconElement.children[0].getAttribute('d')).toBeTruthy(); }); it('should render the title', () => { const iconElement: HTMLElement = fixture.nativeElement; - expect(iconElement.children[0].tagName.toLowerCase()).toEqual("path"); + expect(iconElement.children[0].tagName.toLowerCase()).toEqual('path'); - component.title = "Some title"; + component.title = 'Some title'; fixture.detectChanges(); - expect(iconElement.children[0].tagName.toLowerCase()).toEqual("title"); - expect(iconElement.children[1].tagName.toLowerCase()).toEqual("path"); + expect(iconElement.children[0].tagName.toLowerCase()).toEqual('title'); + expect(iconElement.children[1].tagName.toLowerCase()).toEqual('path'); expect(iconElement.children[1].getAttribute('d')).toBeTruthy(); }); @@ -71,7 +73,7 @@ describe('OpenProject extension icon', () => { it('should render the svg', () => { const iconElement: HTMLElement = fixture.nativeElement; - expect(iconElement.children[0].tagName.toLowerCase()).toEqual("path"); + expect(iconElement.children[0].tagName.toLowerCase()).toEqual('path'); expect(iconElement.children[0].getAttribute('d')).toBeTruthy(); }); @@ -102,9 +104,9 @@ describe('Icon with multiple paths', () => { it('should render the svg with all paths', () => { const iconElement: HTMLElement = fixture.nativeElement; - expect(iconElement.children[0].tagName.toLowerCase()).toEqual("path"); + expect(iconElement.children[0].tagName.toLowerCase()).toEqual('path'); expect(iconElement.children[0].getAttribute('d')).toBeTruthy(); - expect(iconElement.children[1].tagName.toLowerCase()).toEqual("path"); + expect(iconElement.children[1].tagName.toLowerCase()).toEqual('path'); expect(iconElement.children[1].getAttribute('d')).toBeTruthy(); }); @@ -115,3 +117,27 @@ describe('Icon with multiple paths', () => { }); }); + +describe('rendering without Angular', () => { + it('should render the SVG', () => { + const rendered = toDOMString(logIconData); + expect(rendered).toContain('`); + expect(rendered).toContain(''); + }); + + it('should render the small SVG', () => { + const rendered = toDOMString(logIconData, 'small'); + expect(rendered).toContain('`); + expect(rendered).toContain(''); + }); + + it('should render the SVG attributes', () => { + const rendered = toDOMString(logIconData, 'medium', { extra: '1' }); + expect(rendered).toContain('`); + expect(rendered).toContain(''); + }); +}); diff --git a/lib/octicons_angular/src/public-api.ts b/lib/octicons_angular/src/public-api.ts index 9ea5ae55b..92c4afae5 100644 --- a/lib/octicons_angular/src/public-api.ts +++ b/lib/octicons_angular/src/public-api.ts @@ -3,3 +3,4 @@ */ export * from './__generated__/icons'; +export * from './helpers';