From f8f2a74b0fca545bf94000a0f7bc4116ec18e21a Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Sun, 11 Oct 2026 17:03:01 +0200 Subject: [PATCH 1/2] fix: let apps register their own icons, keep only D9's own in the library (BITS-206) D9Icon now looks the name up in the Font Awesome library when it renders, so icons an app adds with library.add work. The library only gets the five icons D9 components render themselves. Storybook registers its demo icons in its preview, like an app. The unused numeric-input icon is gone. Co-Authored-By: Claude Opus 5.5 --- .storybook/preview.ts | 10 ++ src/Icon/Icon.stories.ts | 23 ++++- src/Icon/Icon.vue | 33 +++---- src/Icon/custom/numeric-input.d.ts | 15 --- src/Icon/custom/numeric-input.js | 29 ------ src/Icon/registerIcons.ts | 146 ++++------------------------- 6 files changed, 62 insertions(+), 194 deletions(-) delete mode 100644 src/Icon/custom/numeric-input.d.ts delete mode 100644 src/Icon/custom/numeric-input.js diff --git a/.storybook/preview.ts b/.storybook/preview.ts index a735713..7be7689 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,4 +1,14 @@ import "../styles/app.css"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { + faChevronLeft, + faChevronRight, + faEnvelope, + faUser, +} from "@fortawesome/free-solid-svg-icons"; + +// Demo icons the stories use; like any app, Storybook registers its own icons +library.add(faChevronLeft, faChevronRight, faEnvelope, faUser); export const parameters = { controls: { diff --git a/src/Icon/Icon.stories.ts b/src/Icon/Icon.stories.ts index 742f675..0af91fc 100644 --- a/src/Icon/Icon.stories.ts +++ b/src/Icon/Icon.stories.ts @@ -1,5 +1,10 @@ +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faRocket } from "@fortawesome/free-solid-svg-icons"; +import { faGithub } from "@fortawesome/free-brands-svg-icons"; import { D9Icon } from "../index"; -import { icons, brands } from "../index"; + +// Neither icon is in D9's own list: registered here the way an app does +library.add(faRocket, faGithub); export default { title: "Basics/Icon", @@ -9,10 +14,6 @@ export default { options: ["xs", "1x", "2x", "3x", "4x"], control: { type: "inline-radio" }, }, - name: { - options: [...icons, ...brands], - control: { type: "select" }, - }, }, decorators: [ () => ({ @@ -47,3 +48,15 @@ Large.args = { name: "user", size: "2x", }; + +export const AppRegistered = Template.bind({}); +AppRegistered.args = { + name: "rocket", + size: "2x", +}; + +export const AppRegisteredBrand = Template.bind({}); +AppRegisteredBrand.args = { + name: "github", + size: "2x", +}; diff --git a/src/Icon/Icon.vue b/src/Icon/Icon.vue index 0878512..332fae6 100644 --- a/src/Icon/Icon.vue +++ b/src/Icon/Icon.vue @@ -1,15 +1,15 @@ diff --git a/src/Icon/custom/numeric-input.d.ts b/src/Icon/custom/numeric-input.d.ts deleted file mode 100644 index f670aed..0000000 --- a/src/Icon/custom/numeric-input.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - IconDefinition, - IconPrefix, - IconName, -} from "@fortawesome/fontawesome-common-types"; -export const definition: IconDefinition; -export const faCheckDouble: IconDefinition; -export const prefix: IconPrefix; -export const iconName: IconName; -export const width: number; -export const height: number; -export const ligatures: (string | number)[]; -export const unicode: string; -export const svgPathData: string; -export const aliases: (string | number)[]; diff --git a/src/Icon/custom/numeric-input.js b/src/Icon/custom/numeric-input.js deleted file mode 100644 index 0db7433..0000000 --- a/src/Icon/custom/numeric-input.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; - -var prefix = "fas"; -var iconName = "numeric-input"; -var width = 512; -var height = 512; -var aliases = []; -var unicode = ""; -var svgPathData = - "m409.445 111 2.112.004c34.066.146 46.74 3.848 59.517 10.681 13.037 6.973 23.268 17.204 30.24 30.241l.415.782c6.705 12.782 10.271 25.9 10.271 60.847v72.002c-.15 34.066-3.852 46.74-10.685 59.517-6.973 13.037-17.204 23.268-30.241 30.24l-.782.415C457.51 382.434 444.392 386 409.445 386h-306.89l-2.112-.004c-34.066-.146-46.74-3.848-59.517-10.681-13.037-6.973-23.268-17.204-30.24-30.241l-.415-.782C3.566 331.51 0 318.392 0 283.445v-69.89c0-35.66 3.713-48.592 10.685-61.629 6.973-13.037 17.204-23.268 30.241-30.24l.782-.415C54.49 114.566 67.608 111 102.555 111h306.89Zm.806 36H102.555l-2.387.005c-22.69.089-31.789 1.488-39.667 5.128l-.523.246c-.696.332-1.385.683-2.074 1.052-6.764 3.617-11.856 8.71-15.473 15.473l-.282.533c-4.492 8.62-6.116 17.401-6.148 43.312v71.502l.012 3.09c.161 24.044 1.9 32.308 6.418 40.755 3.617 6.764 8.71 11.856 15.473 15.473l.533.282c8.62 4.492 17.401 6.116 43.312 6.149h307.696c27.078 0 35.763-1.677 44.651-6.43 6.764-3.618 11.856-8.71 15.473-15.474l.282-.533c4.492-8.62 6.116-17.401 6.149-43.312v-70.696c0-27.078-1.677-35.763-6.43-44.651-3.618-6.764-8.71-11.856-15.474-15.473l-.533-.282c-8.62-4.492-17.401-6.116-43.312-6.149Zm-164.715 19c11.682 0 22.013 1.976 30.994 5.929 8.98 3.952 16.005 9.38 21.074 16.284 5.07 6.904 7.578 14.797 7.527 23.677.103 8.829-2.92 16.066-9.07 21.713-6.15 5.646-13.935 8.983-23.353 10.01v1.231c12.764 1.386 22.362 5.274 28.794 11.665 6.433 6.39 9.598 14.411 9.496 24.061.051 9.24-2.715 17.427-8.299 24.562-5.584 7.135-13.33 12.73-23.236 16.785-9.907 4.055-21.319 6.083-34.236 6.083-12.454 0-23.506-2.143-33.156-6.43-9.65-4.285-17.227-10.201-22.734-17.747-5.506-7.545-8.286-16.22-8.337-26.025h43.23c.051 3.029.99 5.737 2.817 8.124 1.827 2.386 4.336 4.247 7.527 5.582 3.19 1.334 6.845 2.002 10.962 2.002 3.962 0 7.462-.706 10.498-2.118 3.037-1.411 5.404-3.375 7.102-5.89 1.699-2.515 2.522-5.39 2.47-8.623.052-3.183-.913-6.006-2.894-8.47-1.982-2.464-4.748-4.389-8.299-5.775-3.55-1.386-7.642-2.079-12.274-2.079h-16.057v-29.566h16.057c4.271 0 8.041-.693 11.31-2.08 3.267-1.385 5.815-3.31 7.642-5.774s2.714-5.287 2.663-8.47c.051-3.08-.695-5.8-2.239-8.161-1.544-2.361-3.692-4.21-6.446-5.544-2.753-1.334-5.93-2.002-9.533-2.002-3.912 0-7.398.693-10.46 2.08-3.063 1.385-5.468 3.31-7.218 5.774s-2.65 5.287-2.702 8.47h-41.068c.051-9.65 2.715-18.172 7.99-25.563 5.275-7.392 12.518-13.192 21.73-17.401 9.212-4.21 19.788-6.314 31.728-6.314Zm-133.074 0c12.615 0 23.55 2.017 32.807 6.05 9.257 4.033 16.41 9.698 21.462 16.994 5.051 7.296 7.577 15.851 7.577 25.665 0 6.063-1.257 12.087-3.77 18.073-2.512 5.986-7.025 12.601-13.538 19.846-6.513 7.245-15.77 15.877-27.77 25.896l-14.768 12.331v.925H176V326H55.077v-30.829l58.77-48.709c3.897-3.237 7.243-6.28 10.038-9.133 2.794-2.851 4.948-5.793 6.461-8.824 1.513-3.032 2.27-6.397 2.27-10.097 0-4.059-.86-7.527-2.578-10.404-1.717-2.877-4.09-5.1-7.115-6.667-3.026-1.567-6.513-2.35-10.461-2.35-3.95 0-7.424.796-10.424 2.389-3 1.593-5.32 3.93-6.961 7.013-1.641 3.083-2.462 6.834-2.462 11.253H52c0-11.099 2.487-20.655 7.462-28.67 4.974-8.016 12-14.182 21.076-18.498 9.077-4.316 19.718-6.474 31.924-6.474Z"; - -const definition = { - prefix: prefix, - iconName: iconName, - icon: [width, height, aliases, unicode, svgPathData], -}; - -export default { - definition, - faAlignJustify: definition, - prefix: prefix, - iconName: iconName, - width: width, - height: height, - ligatures: aliases, - unicode: unicode, - svgPathData: svgPathData, - aliases: aliases, -}; diff --git a/src/Icon/registerIcons.ts b/src/Icon/registerIcons.ts index 95e566f..891b3ac 100644 --- a/src/Icon/registerIcons.ts +++ b/src/Icon/registerIcons.ts @@ -1,132 +1,24 @@ import { library } from "@fortawesome/fontawesome-svg-core"; -import * as fasIcons from "@fortawesome/free-solid-svg-icons"; -import * as fabIcon from "@fortawesome/free-brands-svg-icons"; -import numericInput from "./custom/numeric-input.js"; +import { + faCheck, + faChevronDown, + faCircleNotch, + faEllipsis, + faEyeDropper, +} from "@fortawesome/free-solid-svg-icons"; -library.add( - fasIcons.faAlignCenter, - fasIcons.faAlignLeft, - fasIcons.faAlignRight, - fasIcons.faArrowRight, - fasIcons.faArrowUp19, - fasIcons.faAsterisk, - fasIcons.faAt, - fasIcons.faBold, - fasIcons.faBolt, - fasIcons.faCalendar, - fasIcons.faChartPie, - fasIcons.faCheck, - fasIcons.faCheckCircle, - fasIcons.faChevronDown, - fasIcons.faChevronLeft, - fasIcons.faChevronRight, - fasIcons.faChevronUp, - fasIcons.faCircleCheck, - fasIcons.faCircleNodes, - fasIcons.faCircleNotch, - fasIcons.faClipboard, - fasIcons.faCloudDownload, - fasIcons.faCloudUpload, - fasIcons.faCode, - fasIcons.faCodeBranch, - fasIcons.faCodeFork, - fasIcons.faCog, - fasIcons.faCompass, - fasIcons.faCrown, - fasIcons.faDiagramProject, - fasIcons.faDoorOpen, - fasIcons.faDotCircle, - fasIcons.faEllipsis, - fasIcons.faEllipsisVertical, - fasIcons.faEnvelope, - fasIcons.faExclamationTriangle, - fasIcons.faExternalLink, - fasIcons.faEye, - fasIcons.faEyeDropper, - fasIcons.faEyeSlash, - fasIcons.faFileLines, - fasIcons.faFilePdf, - fasIcons.faFileUpload, - fasIcons.faFileVideo, - fasIcons.faFileWaveform, - fasIcons.faFilter, - fasIcons.faFilter, - fasIcons.faFingerprint, - fasIcons.faFont, - fasIcons.faGears, - fasIcons.faGlobe, - fasIcons.faGripHorizontal, - fasIcons.faGripVertical, - fasIcons.faHandPointRight, - fasIcons.faHandPointRight, - fasIcons.faHashtag, - fasIcons.faHeading, - fasIcons.faHeart, - fasIcons.faImage, - fasIcons.faInfoCircle, - fasIcons.faItalic, - fasIcons.faLayerGroup, - fasIcons.faLink, - fasIcons.faLinkSlash, - fasIcons.faList, - fasIcons.faListOl, - fasIcons.faListUl, - fasIcons.faLocationArrow, - fasIcons.faLocationCrosshairs, - fasIcons.faLock, - fasIcons.faLockOpen, - fasIcons.faMedal, - fasIcons.faMessage, - fasIcons.faMicrochip, - fasIcons.faPencilAlt, - fasIcons.faPenToSquare, - fasIcons.faPhone, - fasIcons.faPlayCircle, - fasIcons.faPlus, - fasIcons.faPlusCircle, - fasIcons.faPoo, - fasIcons.faPuzzlePiece, - fasIcons.faQuestion, - fasIcons.faQuoteLeft, - fasIcons.faQuoteRight, - fasIcons.faReply, - fasIcons.faReplyAll, - fasIcons.faRightLong, - fasIcons.faRobot, - fasIcons.faSave, - fasIcons.faShield, - fasIcons.faSignal, - fasIcons.faSignature, - fasIcons.faSignature, - fasIcons.faSquareCheck, - fasIcons.faStar, - fasIcons.faStrikethrough, - fasIcons.faTerminal, - fasIcons.faTextSlash, - fasIcons.faTimes, - fasIcons.faTrash, - fasIcons.faTree, - fasIcons.faTrophy, - fasIcons.faTurnDown, - fasIcons.faUnderline, - fasIcons.faUpload, - fasIcons.faUser, - fasIcons.faUserShield, - fasIcons.faWandMagicSparkles, +// Only the icons D9 components render themselves; apps register every other icon with library.add +const definitions = [ + faCheck, + faChevronDown, + faCircleNotch, + faEllipsis, + faEyeDropper, +]; - // Brands - fabIcon.faTwitter, - fabIcon.faInstagram, - fabIcon.faGithub, - fabIcon.faLinkedin, - fabIcon.faFacebook, +library.add(...definitions); - // Custom - numericInput.definition -); +export default definitions.map(({ iconName }): string => iconName); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export default [...Object.keys((library as any).definitions.fas)].sort(); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const brands = [...Object.keys((library as any).definitions.fab)].sort(); +// Kept for apps that import it; D9 registers no brand icons +export const brands: string[] = []; From dfae9675be07297d138bcc30f3e84e59778b27d1 Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Sun, 11 Oct 2026 17:10:53 +0200 Subject: [PATCH 2/2] test: use GitLab in the brand-icon spec so it needs the render-time lookup (BITS-206) GitHub was in D9's old icon list, so the spec passed without the fix. GitLab never was: the spec now fails on main and passes with this branch. Co-Authored-By: Claude Opus 5.5 --- tests/Icon.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Icon.spec.ts b/tests/Icon.spec.ts index 2541023..8b9ce17 100644 --- a/tests/Icon.spec.ts +++ b/tests/Icon.spec.ts @@ -1,18 +1,18 @@ import { library } from "@fortawesome/fontawesome-svg-core"; -import { faGithub } from "@fortawesome/free-brands-svg-icons"; +import { faGitlab } from "@fortawesome/free-brands-svg-icons"; import { mount } from "@vue/test-utils"; import { describe, expect, it, vi } from "vitest"; import { D9Icon } from "../src"; describe("D9Icon", () => { it("renders a brand icon with the fab prefix", () => { - // Registered here like an app does, so the spec doesn't rely on D9's own icon list - library.add(faGithub); - const wrapper = mount(D9Icon, { props: { name: "github" } }); + // Registered here like an app does; GitLab was never in D9's own icon list + library.add(faGitlab); + const wrapper = mount(D9Icon, { props: { name: "gitlab" } }); const svg = wrapper.find("svg"); expect(svg.attributes("data-prefix")).toBe("fab"); - expect(svg.attributes("data-icon")).toBe("github"); + expect(svg.attributes("data-icon")).toBe("gitlab"); }); it("renders nothing for an unknown name", () => {