diff --git a/src/Umbraco.Web.UI.Client/.storybook/main.ts b/src/Umbraco.Web.UI.Client/.storybook/main.ts index 5b44d4555b2b..57ca504f7aaa 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/main.ts +++ b/src/Umbraco.Web.UI.Client/.storybook/main.ts @@ -33,6 +33,10 @@ const config: StorybookConfig = { from: '../src/packages/core/icon-registry/icons', to: 'assets/icons', }, + { + from: '../node_modules/lucide-static', + to: 'lucide-static', + }, ], typescript: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/available-lucide-icons.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/available-lucide-icons.stories.ts new file mode 100644 index 000000000000..e5ab6ad41f05 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/available-lucide-icons.stories.ts @@ -0,0 +1,137 @@ +import iconDictionary from './icon-dictionary.json'; +import type { Meta, StoryObj } from '@storybook/web-components-vite'; + +export default { + title: 'Generic Components/Icon/Available Lucide Icons', + id: 'available-lucide-icons', +} as Meta; + +interface UnregisteredIcon { + name: string; + svgPath: string; + inUse?: boolean; +} + +type LucideIconsData = Record; + +interface IconsResult { + icons: UnregisteredIcon[]; + stats: { + total: number; + registered: number; + unregistered: number; + }; +} + +/** + * Get all unregistered Lucide icons + * @returns Promise with icons and statistics + */ +async function getUnregisteredIcons(): Promise { + // Get all registered lucide icon file names (without .svg extension) + const registeredFiles = new Set( + iconDictionary.lucide + .filter((icon) => icon.file) // Filter out entries without a file property + .map((icon) => icon.file?.replace('.svg', '')), + ); + + try { + // Fetch lucide icons from static directory + const response = await fetch('/lucide-static/icon-nodes.json'); + const lucideIcons: LucideIconsData = await response.json(); + + const icons: UnregisteredIcon[] = Object.keys(lucideIcons) + .map((lucideName) => ({ + name: lucideName, + svgPath: `/lucide-static/icons/${lucideName}.svg`, + inUse: registeredFiles.has(lucideName), + })) + .sort((a, b) => a.name.localeCompare(b.name)); + + return { + icons, + stats: { + total: Object.keys(lucideIcons).length, + registered: iconDictionary.lucide.filter((icon) => icon.file).length, + unregistered: icons.filter((icon) => !icon.inUse).length, + }, + }; + } catch (error) { + console.error('Failed to load lucide icons:', error); + return { + icons: [], + stats: { total: 0, registered: iconDictionary.lucide.filter((icon) => icon.file).length, unregistered: 0 }, + }; + } +} + +export const Docs: StoryObj = { + render: () => { + // Create a promise-based approach + const data = getUnregisteredIcons(); + + // Return immediately with a loading state, then update + const container = document.createElement('div'); + container.innerHTML = '

Loading...

'; + + data.then(({ icons, stats }) => { + const grid = icons.map( + (icon) => ` +
+ ${icon.name} + ${icon.name} +
+ `, + ); + + container.innerHTML = ` +
+

Available Lucide Icons

+

+ Total Lucide Icons: ${stats.total}
+ Registered in CMS: ${stats.registered} (Marked with blue)
+ Unregistered: ${stats.unregistered} +

+

+ These icons are available in Lucide but not yet registered in the Umbraco CMS icon registry. + You can contribute new icon registrations by making a PR to the CMS source code. In the CMS Source Code add the desired icon by adding it to the 'icon-dictionary.json', under 'lucide'. Afterwards run 'npm run generate:icons'. + Note: You can also add icons just in your own project. For more information, see the Umbraco Icons Extension documentation. +

+
+
+ ${grid.join('')} +
+ `; + }); + + return container; + }, +};