Ditch the abandoned ngx-bootstrap-icons by rolling our own

This commit is contained in:
shamoon committed 2026-11-02 19:22:11 -08:00
1 parent 16b79fc023
commit 7d9dabeb17
9 files changed
+443 -19

No files matched your search

+3
View File
@@ -69,6 +69,9 @@
"src/polyfills.ts"
],
"tsConfig": "tsconfig.app.json",
"loader": {
".svg": "text"
},
"localize": true,
"assets": [
"src/apple-touch-icon.png",
+1 -1
View File
@@ -28,7 +28,7 @@ module.exports = {
'abstract-paperless-service',
],
transformIgnorePatterns: [
'node_modules/(?!.*(\\.mjs$|tslib|lodash-es|normalize-diacritics|marked|@angular/common/locales/.*\\.js$))',
'node_modules/(?!.*(\\.mjs$|tslib|lodash-es|normalize-diacritics|marked|@angular/common/locales/.*\\.js$|bootstrap-icons/icons/.*\\.svg$))',
],
moduleNameMapper: {
...esmPreset.moduleNameMapper,
+1 -1
View File
@@ -28,11 +28,11 @@
"@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.2",
"file-saver": "^2.0.5",
"lodash-es": "^4.18.1",
"marked": "~18.1.1",
"mime-names": "^1.0.0",
"ngx-bootstrap-icons": "^1.9.3",
"ngx-color": "^10.1.0",
"ngx-cookie-service": "^22.0.0",
"ngx-device-detector": "^12.0.0",
+8 -16
View File
@@ -51,6 +51,9 @@ importers:
bootstrap:
specifier: ^5.3.8
version: 5.3.8(@popperjs/core@2.11.8)
bootstrap-icons:
specifier: ^1.13.2
version: 1.13.2
file-saver:
specifier: ^2.0.5
version: 2.0.5
@@ -63,9 +66,6 @@ importers:
mime-names:
specifier: ^1.0.0
version: 1.0.0
ngx-bootstrap-icons:
specifier: ^1.9.3
version: 1.9.3(@angular/common@22.2.3(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))
ngx-color:
specifier: ^10.1.0
version: 10.1.0(@angular/common@22.2.3(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))
@@ -2391,6 +2391,9 @@ packages:
resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==}
engines: {node: '>=20.19.0'}
bootstrap-icons@1.13.2:
resolution: {integrity: sha512-fAU1Ly3dH7hwRg+CB8EZ0Vl0fF80zgXHeUVJyfUOKzoURr+TbwISbCW5tKUoQLERaWYG2emTCybcPbD96hdD1A==}
bootstrap@5.3.8:
resolution: {integrity: sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==}
peerDependencies:
@@ -3421,13 +3424,6 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
ngx-bootstrap-icons@1.9.3:
resolution: {integrity: sha512-UsFqJ/cn0u5W39hVMIDbm+ze1dCF9fDV839scqeimi70Efcmg41zOx6GgR6i2gWAVFR0OBso1cdqb4E75XhTSw==}
engines: {node: '>= 16.18.1', npm: '>= 8.11.0'}
peerDependencies:
'@angular/common': '>= 13.3.8'
'@angular/core': '>= 13.3.8'
ngx-color@10.1.0:
resolution: {integrity: sha512-yuxlE1LfN+PABIO4hv0Gw5ksq8g9XkMJKrGPn2SWbrre/gsH+kkyyn3rwSCurinnLCnw7czqenwQUtnBJm8yww==}
peerDependencies:
@@ -6411,6 +6407,8 @@ snapshots:
boolbase@2.0.0: {}
bootstrap-icons@1.13.2: {}
bootstrap@5.3.8(@popperjs/core@2.11.8):
dependencies:
'@popperjs/core': 2.11.8
@@ -7648,12 +7646,6 @@ snapshots:
neo-async@2.6.2: {}
ngx-bootstrap-icons@1.9.3(@angular/common@22.2.3(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2)):
dependencies:
'@angular/common': 22.2.3(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))(rxjs@7.8.2)
'@angular/core': 22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2)
tslib: 2.8.1
ngx-color@10.1.0(@angular/common@22.2.3(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2)):
dependencies:
'@angular/common': 22.2.3(@angular/core@22.2.3(@angular/compiler@22.2.3)(rxjs@7.8.2))(rxjs@7.8.2)
@@ -0,0 +1,50 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { BootstrapIconComponent } from './bootstrap-icon.component'
describe('BootstrapIconComponent', () => {
let fixture: ComponentFixture<BootstrapIconComponent>
beforeEach(() => {
TestBed.configureTestingModule({
imports: [BootstrapIconComponent],
})
fixture = TestBed.createComponent(BootstrapIconComponent)
})
it('should render the named icon as inline svg', () => {
fixture.componentRef.setInput('name', 'arrow-down-up')
fixture.detectChanges()
const svg: SVGElement = fixture.nativeElement.querySelector('svg')
expect(svg.classList).toContain('bi-arrow-down-up')
expect(svg.getAttribute('width')).toEqual('16')
expect(svg.getAttribute('height')).toEqual('16')
})
it('should apply width and height', () => {
fixture.componentRef.setInput('name', 'x-lg')
fixture.componentRef.setInput('width', '1.2em')
fixture.componentRef.setInput('height', 2)
fixture.detectChanges()
const svg: SVGElement = fixture.nativeElement.querySelector('svg')
expect(svg.getAttribute('width')).toEqual('1.2em')
expect(svg.getAttribute('height')).toEqual('2')
})
it('should update when the name changes', () => {
fixture.componentRef.setInput('name', 'x-lg')
fixture.detectChanges()
fixture.componentRef.setInput('name', 'sliders2-vertical')
fixture.detectChanges()
expect(fixture.nativeElement.querySelector('svg').classList).toContain(
'bi-sliders2-vertical'
)
})
it('should render nothing and warn for an unregistered icon', () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
fixture.componentRef.setInput('name', 'not-an-icon')
fixture.detectChanges()
expect(fixture.nativeElement.innerHTML).toEqual('')
expect(warnSpy).toHaveBeenCalledWith('Icon not found: not-an-icon')
})
})
@@ -0,0 +1,59 @@
import {
ChangeDetectionStrategy,
Component,
effect,
ElementRef,
inject,
input,
} from '@angular/core'
import { icons } from './icons'
const DEFAULT_SIZE = '16'
/** Converts icon name e.g. `arrow-down-up` to its registry key. */
const toKey = (name: string) =>
name.trim().replace(/-(.)/g, (_, char: string) => char.toUpperCase())
/**
* Renders an icon from the official bootstrap-icons package as inline SVG.
* Icons must be registered in `./icons.ts`.
*/
@Component({
// keep the selector of the ngx-bootstrap-icons
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'i-bs',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BootstrapIconComponent {
private readonly elementRef = inject(ElementRef<HTMLElement>)
readonly name = input<string>()
readonly width = input<string | number>()
readonly height = input<string | number>()
constructor() {
effect(() => {
this.elementRef.nativeElement.innerHTML = this.svg()
})
}
private svg(): string {
const name = this.name()
if (!name) return ''
let svg = icons[toKey(name)]
if (!svg) {
console.warn(`Icon not found: ${name}`)
return ''
}
const width = this.width()
const height = this.height()
if (width) {
svg = svg.replace(`width="${DEFAULT_SIZE}"`, `width="${width}"`)
}
if (height) {
svg = svg.replace(`height="${DEFAULT_SIZE}"`, `height="${height}"`)
}
return svg
}
}
@@ -0,0 +1,315 @@
import airplane from 'bootstrap-icons/icons/airplane.svg'
import archive from 'bootstrap-icons/icons/archive.svg'
import arrowClockwise from 'bootstrap-icons/icons/arrow-clockwise.svg'
import arrowCounterclockwise from 'bootstrap-icons/icons/arrow-counterclockwise.svg'
import arrowDownUp from 'bootstrap-icons/icons/arrow-down-up.svg'
import arrowDown from 'bootstrap-icons/icons/arrow-down.svg'
import arrowLeft from 'bootstrap-icons/icons/arrow-left.svg'
import arrowRepeat from 'bootstrap-icons/icons/arrow-repeat.svg'
import arrowReturnLeft from 'bootstrap-icons/icons/arrow-return-left.svg'
import arrowRightShort from 'bootstrap-icons/icons/arrow-right-short.svg'
import arrowRight from 'bootstrap-icons/icons/arrow-right.svg'
import arrowUpRight from 'bootstrap-icons/icons/arrow-up-right.svg'
import asterisk from 'bootstrap-icons/icons/asterisk.svg'
import bank from 'bootstrap-icons/icons/bank.svg'
import basket from 'bootstrap-icons/icons/basket.svg'
import bell from 'bootstrap-icons/icons/bell.svg'
import bodyText from 'bootstrap-icons/icons/body-text.svg'
import bookmark from 'bootstrap-icons/icons/bookmark.svg'
import boxArrowInRight from 'bootstrap-icons/icons/box-arrow-in-right.svg'
import boxArrowUpRight from 'bootstrap-icons/icons/box-arrow-up-right.svg'
import boxArrowUp from 'bootstrap-icons/icons/box-arrow-up.svg'
import boxes from 'bootstrap-icons/icons/boxes.svg'
import braces from 'bootstrap-icons/icons/braces.svg'
import briefcase from 'bootstrap-icons/icons/briefcase.svg'
import building from 'bootstrap-icons/icons/building.svg'
import calculator from 'bootstrap-icons/icons/calculator.svg'
import calendarEventFill from 'bootstrap-icons/icons/calendar-event-fill.svg'
import calendarEvent from 'bootstrap-icons/icons/calendar-event.svg'
import calendar from 'bootstrap-icons/icons/calendar.svg'
import camera from 'bootstrap-icons/icons/camera.svg'
import cardChecklist from 'bootstrap-icons/icons/card-checklist.svg'
import cardHeading from 'bootstrap-icons/icons/card-heading.svg'
import caretDown from 'bootstrap-icons/icons/caret-down.svg'
import caretUp from 'bootstrap-icons/icons/caret-up.svg'
import cash from 'bootstrap-icons/icons/cash.svg'
import chatLeftText from 'bootstrap-icons/icons/chat-left-text.svg'
import chatSquareDots from 'bootstrap-icons/icons/chat-square-dots.svg'
import checkAll from 'bootstrap-icons/icons/check-all.svg'
import checkCircleFill from 'bootstrap-icons/icons/check-circle-fill.svg'
import checkCircle from 'bootstrap-icons/icons/check-circle.svg'
import checkLg from 'bootstrap-icons/icons/check-lg.svg'
import check from 'bootstrap-icons/icons/check.svg'
import check2All from 'bootstrap-icons/icons/check2-all.svg'
import chevronDoubleLeft from 'bootstrap-icons/icons/chevron-double-left.svg'
import chevronDoubleRight from 'bootstrap-icons/icons/chevron-double-right.svg'
import chevronDown from 'bootstrap-icons/icons/chevron-down.svg'
import chevronRight from 'bootstrap-icons/icons/chevron-right.svg'
import chevronUp from 'bootstrap-icons/icons/chevron-up.svg'
import circle from 'bootstrap-icons/icons/circle.svg'
import clipboardCheckFill from 'bootstrap-icons/icons/clipboard-check-fill.svg'
import clipboardCheck from 'bootstrap-icons/icons/clipboard-check.svg'
import clipboardFill from 'bootstrap-icons/icons/clipboard-fill.svg'
import clipboard from 'bootstrap-icons/icons/clipboard.svg'
import clockHistory from 'bootstrap-icons/icons/clock-history.svg'
import cpu from 'bootstrap-icons/icons/cpu.svg'
import creditCard from 'bootstrap-icons/icons/credit-card.svg'
import dashCircle from 'bootstrap-icons/icons/dash-circle.svg'
import dash from 'bootstrap-icons/icons/dash.svg'
import diagram3 from 'bootstrap-icons/icons/diagram-3.svg'
import dice5 from 'bootstrap-icons/icons/dice-5.svg'
import doorOpen from 'bootstrap-icons/icons/door-open.svg'
import download from 'bootstrap-icons/icons/download.svg'
import envelopeAtFill from 'bootstrap-icons/icons/envelope-at-fill.svg'
import envelopeAt from 'bootstrap-icons/icons/envelope-at.svg'
import envelope from 'bootstrap-icons/icons/envelope.svg'
import exclamationCircleFill from 'bootstrap-icons/icons/exclamation-circle-fill.svg'
import exclamationTriangleFill from 'bootstrap-icons/icons/exclamation-triangle-fill.svg'
import exclamationTriangle from 'bootstrap-icons/icons/exclamation-triangle.svg'
import eye from 'bootstrap-icons/icons/eye.svg'
import fileEarmarkArrowUp from 'bootstrap-icons/icons/file-earmark-arrow-up.svg'
import fileEarmarkCheck from 'bootstrap-icons/icons/file-earmark-check.svg'
import fileEarmarkDiff from 'bootstrap-icons/icons/file-earmark-diff.svg'
import fileEarmarkFill from 'bootstrap-icons/icons/file-earmark-fill.svg'
import fileEarmarkLock from 'bootstrap-icons/icons/file-earmark-lock.svg'
import fileEarmarkMedical from 'bootstrap-icons/icons/file-earmark-medical.svg'
import fileEarmarkMinus from 'bootstrap-icons/icons/file-earmark-minus.svg'
import fileEarmarkPerson from 'bootstrap-icons/icons/file-earmark-person.svg'
import fileEarmarkPlus from 'bootstrap-icons/icons/file-earmark-plus.svg'
import fileEarmarkRichtext from 'bootstrap-icons/icons/file-earmark-richtext.svg'
import fileEarmarkSpreadsheet from 'bootstrap-icons/icons/file-earmark-spreadsheet.svg'
import fileEarmark from 'bootstrap-icons/icons/file-earmark.svg'
import fileText from 'bootstrap-icons/icons/file-text.svg'
import files from 'bootstrap-icons/icons/files.svg'
import filter from 'bootstrap-icons/icons/filter.svg'
import folderFill from 'bootstrap-icons/icons/folder-fill.svg'
import folder from 'bootstrap-icons/icons/folder.svg'
import funnel from 'bootstrap-icons/icons/funnel.svg'
import gear from 'bootstrap-icons/icons/gear.svg'
import globe2 from 'bootstrap-icons/icons/globe2.svg'
import google from 'bootstrap-icons/icons/google.svg'
import grid from 'bootstrap-icons/icons/grid.svg'
import gripVertical from 'bootstrap-icons/icons/grip-vertical.svg'
import hash from 'bootstrap-icons/icons/hash.svg'
import hddStack from 'bootstrap-icons/icons/hdd-stack.svg'
import heart from 'bootstrap-icons/icons/heart.svg'
import house from 'bootstrap-icons/icons/house.svg'
import inbox from 'bootstrap-icons/icons/inbox.svg'
import infoCircle from 'bootstrap-icons/icons/info-circle.svg'
import journalBookmarkFill from 'bootstrap-icons/icons/journal-bookmark-fill.svg'
import journals from 'bootstrap-icons/icons/journals.svg'
import lightbulb from 'bootstrap-icons/icons/lightbulb.svg'
import link from 'bootstrap-icons/icons/link.svg'
import listNested from 'bootstrap-icons/icons/list-nested.svg'
import listTask from 'bootstrap-icons/icons/list-task.svg'
import listUl from 'bootstrap-icons/icons/list-ul.svg'
import list from 'bootstrap-icons/icons/list.svg'
import microsoft from 'bootstrap-icons/icons/microsoft.svg'
import newspaper from 'bootstrap-icons/icons/newspaper.svg'
import nodePlus from 'bootstrap-icons/icons/node-plus.svg'
import paperclip from 'bootstrap-icons/icons/paperclip.svg'
import pencil from 'bootstrap-icons/icons/pencil.svg'
import peopleFill from 'bootstrap-icons/icons/people-fill.svg'
import people from 'bootstrap-icons/icons/people.svg'
import personCircle from 'bootstrap-icons/icons/person-circle.svg'
import personFillLock from 'bootstrap-icons/icons/person-fill-lock.svg'
import personFill from 'bootstrap-icons/icons/person-fill.svg'
import personLock from 'bootstrap-icons/icons/person-lock.svg'
import personSquare from 'bootstrap-icons/icons/person-square.svg'
import person from 'bootstrap-icons/icons/person.svg'
import playFill from 'bootstrap-icons/icons/play-fill.svg'
import plusCircle from 'bootstrap-icons/icons/plus-circle.svg'
import plus from 'bootstrap-icons/icons/plus.svg'
import printer from 'bootstrap-icons/icons/printer.svg'
import questionCircle from 'bootstrap-icons/icons/question-circle.svg'
import receipt from 'bootstrap-icons/icons/receipt.svg'
import safe from 'bootstrap-icons/icons/safe.svg'
import scissors from 'bootstrap-icons/icons/scissors.svg'
import search from 'bootstrap-icons/icons/search.svg'
import send from 'bootstrap-icons/icons/send.svg'
import shop from 'bootstrap-icons/icons/shop.svg'
import slashCircle from 'bootstrap-icons/icons/slash-circle.svg'
import sliders2Vertical from 'bootstrap-icons/icons/sliders2-vertical.svg'
import sortAlphaDown from 'bootstrap-icons/icons/sort-alpha-down.svg'
import sortAlphaUpAlt from 'bootstrap-icons/icons/sort-alpha-up-alt.svg'
import stack from 'bootstrap-icons/icons/stack.svg'
import stars from 'bootstrap-icons/icons/stars.svg'
import tagFill from 'bootstrap-icons/icons/tag-fill.svg'
import tag from 'bootstrap-icons/icons/tag.svg'
import tags from 'bootstrap-icons/icons/tags.svg'
import telephone from 'bootstrap-icons/icons/telephone.svg'
import textIndentLeft from 'bootstrap-icons/icons/text-indent-left.svg'
import textLeft from 'bootstrap-icons/icons/text-left.svg'
import threeDotsVertical from 'bootstrap-icons/icons/three-dots-vertical.svg'
import threeDots from 'bootstrap-icons/icons/three-dots.svg'
import trash from 'bootstrap-icons/icons/trash.svg'
import truck from 'bootstrap-icons/icons/truck.svg'
import uiRadios from 'bootstrap-icons/icons/ui-radios.svg'
import unlock from 'bootstrap-icons/icons/unlock.svg'
import upcScan from 'bootstrap-icons/icons/upc-scan.svg'
import wallet2 from 'bootstrap-icons/icons/wallet2.svg'
import windowStack from 'bootstrap-icons/icons/window-stack.svg'
import xCircle from 'bootstrap-icons/icons/x-circle.svg'
import xLg from 'bootstrap-icons/icons/x-lg.svg'
import x from 'bootstrap-icons/icons/x.svg'
/**
* `<i-bs>`. Only icons registered here are bundled but can add
* new ones (any icon from the bootstrap-icons package) as needed.
*/
export const icons: Record<string, string> = {
airplane,
archive,
arrowClockwise,
arrowCounterclockwise,
arrowDown,
arrowDownUp,
arrowLeft,
arrowRepeat,
arrowReturnLeft,
arrowRight,
arrowRightShort,
arrowUpRight,
asterisk,
bank,
basket,
bell,
braces,
bodyText,
bookmark,
boxArrowInRight,
boxArrowUp,
boxArrowUpRight,
boxes,
briefcase,
building,
calculator,
calendar,
calendarEvent,
calendarEventFill,
camera,
cardChecklist,
cardHeading,
caretDown,
caretUp,
chatLeftText,
chatSquareDots,
check,
check2All,
checkAll,
checkCircle,
checkCircleFill,
checkLg,
chevronDoubleLeft,
chevronDoubleRight,
chevronDown,
chevronRight,
chevronUp,
circle,
clipboard,
clipboardCheck,
clipboardCheckFill,
clipboardFill,
clockHistory,
cpu,
cash,
creditCard,
dash,
dashCircle,
diagram3,
dice5,
doorOpen,
download,
envelope,
envelopeAt,
envelopeAtFill,
exclamationCircleFill,
exclamationTriangle,
exclamationTriangleFill,
eye,
fileEarmark,
fileEarmarkArrowUp,
fileEarmarkCheck,
fileEarmarkDiff,
fileEarmarkFill,
fileEarmarkLock,
fileEarmarkMedical,
fileEarmarkMinus,
fileEarmarkPerson,
fileEarmarkPlus,
fileEarmarkRichtext,
fileEarmarkSpreadsheet,
files,
fileText,
filter,
folder,
folderFill,
funnel,
gear,
globe2,
google,
grid,
gripVertical,
hash,
hddStack,
heart,
house,
inbox,
infoCircle,
journalBookmarkFill,
journals,
lightbulb,
link,
list,
listNested,
listTask,
listUl,
microsoft,
newspaper,
nodePlus,
pencil,
paperclip,
people,
peopleFill,
person,
personCircle,
personFill,
personFillLock,
personLock,
personSquare,
playFill,
plus,
plusCircle,
printer,
questionCircle,
receipt,
safe,
scissors,
search,
send,
slashCircle,
shop,
sliders2Vertical,
sortAlphaDown,
sortAlphaUpAlt,
stack,
stars,
tagFill,
tag,
tags,
textIndentLeft,
textLeft,
telephone,
threeDots,
threeDotsVertical,
trash,
truck,
uiRadios,
unlock,
upcScan,
wallet2,
windowStack,
x,
xCircle,
xLg,
}
+1 -1
View File
@@ -6,7 +6,7 @@ export const DEFAULT_APP_TITLE = 'Paperless-ngx'
export const environment = {
production: false,
apiBaseUrl: 'http://localhost:8000/api/',
apiBaseUrl: '/api/',
apiVersion: '10',
appTitle: DEFAULT_APP_TITLE,
tag: 'dev',
+5
View File
@@ -0,0 +1,5 @@
// SVGs are imported as text (see the `loader` option in angular.json)
declare module '*.svg' {
const content: string
export default content
}