diff --git a/package-lock.json b/package-lock.json
index 5f3adef..1c91b97 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@angular/platform-browser": "21.1.5",
"@angular/router": "21.1.5",
"@angular/service-worker": "21.1.5",
+ "@lucide/angular": "^1.25.0",
"primeicons": "^7.0.0",
"primeng": "^21.0.3",
"rxjs": "~7.8.0",
@@ -2009,6 +2010,19 @@
"win32"
]
},
+ "node_modules/@lucide/angular": {
+ "version": "1.25.0",
+ "resolved": "https://registry.npmjs.org/@lucide/angular/-/angular-1.25.0.tgz",
+ "integrity": "sha512-Hu1eHlGIeyamhSCFEO5YmiUlWn/h4y9BOXMDndY0HRu05CNeCt5BENkfBoeE/bzggMGGbYG+SOBkQUG939tFXw==",
+ "license": "ISC",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": ">=17.0.0",
+ "@angular/core": ">=17.0.0"
+ }
+ },
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.26.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
diff --git a/package.json b/package.json
index 98cee47..d25036d 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"@angular/platform-browser": "21.1.5",
"@angular/router": "21.1.5",
"@angular/service-worker": "21.1.5",
+ "@lucide/angular": "^1.25.0",
"primeicons": "^7.0.0",
"primeng": "^21.0.3",
"rxjs": "~7.8.0",
diff --git a/src/app/shared/ui/icon/icon-registry.ts b/src/app/shared/ui/icon/icon-registry.ts
new file mode 100644
index 0000000..1a7c2b2
--- /dev/null
+++ b/src/app/shared/ui/icon/icon-registry.ts
@@ -0,0 +1,162 @@
+import {
+ LucideAlignLeft as AlignLeft,
+ LucideArrowDown as ArrowDown,
+ LucideArrowLeft as ArrowLeft,
+ LucideArrowRight as ArrowRight,
+ LucideArrowUp as ArrowUp,
+ LucideBadgeCheck as BadgeCheck,
+ LucideBarChart3 as BarChart3,
+ LucideBell as Bell,
+ LucideBook as Book,
+ LucideBoxes as Boxes,
+ LucideBuilding2 as Building2,
+ LucideCalendar as Calendar,
+ LucideCheck as Check,
+ LucideCheckCircle2 as CheckCircle2,
+ LucideChevronDown as ChevronDown,
+ LucideChevronLeft as ChevronLeft,
+ LucideChevronRight as ChevronRight,
+ LucideChevronUp as ChevronUp,
+ LucideCircle as Circle,
+ LucideClock as Clock,
+ LucideCode2 as Code2,
+ LucideCompass as Compass,
+ LucideCopy as Copy,
+ LucideCreditCard as CreditCard,
+ LucideDatabase as Database,
+ LucideDownload as Download,
+ LucideFilter as Filter,
+ LucideFlag as Flag,
+ LucideFolder as Folder,
+ LucideGlobe as Globe,
+ LucideHeart as Heart,
+ LucideHelpCircle as HelpCircle,
+ LucideHistory as History,
+ LucideHome as Home,
+ LucideImage as Image,
+ LucideImages as Images,
+ LucideInfo as Info,
+ LucideLayoutGrid as LayoutGrid,
+ LucideLineChart as LineChart,
+ LucideLink as Link,
+ LucideList as List,
+ LucideLoader2 as Loader2,
+ LucideLock as Lock,
+ LucideLogOut as LogOut,
+ LucideMegaphone as Megaphone,
+ LucideMenu as Menu,
+ LucideMinus as Minus,
+ LucideMonitor as Monitor,
+ LucideNetwork as Network,
+ LucidePackage as Package,
+ LucidePalette as Palette,
+ LucidePencil as Pencil,
+ LucidePlus as Plus,
+ LucidePlusCircle as PlusCircle,
+ LucideSave as Save,
+ LucideScale as Scale,
+ LucideSearch as Search,
+ LucideSettings as Settings,
+ LucideShield as Shield,
+ LucideShoppingCart as ShoppingCart,
+ LucideSlidersHorizontal as SlidersHorizontal,
+ LucideSquare as Square,
+ LucideStar as Star,
+ LucideStore as Store,
+ LucideTable as Table,
+ LucideTag as Tag,
+ LucideTags as Tags,
+ LucideTrash2 as Trash2,
+ LucideTriangleAlert as TriangleAlert,
+ LucideUpload as Upload,
+ LucideUser as User,
+ LucideUsers as Users,
+ LucideVideo as Video,
+ LucideX as X,
+ LucideXCircle as XCircle
+} from '@lucide/angular';
+
+/**
+ * Canonical name -> Lucide icon map for the whole app (storefront + builder + backoffice).
+ * One entry per concept so every screen renders the same icon for the same meaning
+ * (RC UI Sprint - Icon & Visual Language Audit). Extend this list rather than
+ * importing Lucide icons ad hoc in individual components.
+ */
+export const APP_ICONS = {
+ home: Home,
+ store: Store,
+ cart: ShoppingCart,
+ package: Package,
+ boxes: Boxes,
+ folder: Folder,
+ tag: Tag,
+ tags: Tags,
+ users: Users,
+ user: User,
+ settings: Settings,
+ chartBar: BarChart3,
+ chartLine: LineChart,
+ search: Search,
+ filter: Filter,
+ heart: Heart,
+ scale: Scale,
+ bell: Bell,
+ image: Image,
+ images: Images,
+ palette: Palette,
+ monitor: Monitor,
+ shield: Shield,
+ lock: Lock,
+ database: Database,
+ upload: Upload,
+ download: Download,
+ edit: Pencil,
+ trash: Trash2,
+ copy: Copy,
+ plus: Plus,
+ plusCircle: PlusCircle,
+ minus: Minus,
+ check: Check,
+ checkCircle: CheckCircle2,
+ x: X,
+ xCircle: XCircle,
+ arrowUp: ArrowUp,
+ arrowDown: ArrowDown,
+ arrowLeft: ArrowLeft,
+ arrowRight: ArrowRight,
+ chevronUp: ChevronUp,
+ chevronDown: ChevronDown,
+ chevronLeft: ChevronLeft,
+ chevronRight: ChevronRight,
+ info: Info,
+ help: HelpCircle,
+ warning: TriangleAlert,
+ clock: Clock,
+ calendar: Calendar,
+ menu: Menu,
+ alignLeft: AlignLeft,
+ book: Book,
+ building: Building2,
+ code: Code2,
+ compass: Compass,
+ creditCard: CreditCard,
+ flag: Flag,
+ globe: Globe,
+ history: History,
+ link: Link,
+ list: List,
+ megaphone: Megaphone,
+ logOut: LogOut,
+ network: Network,
+ slidersHorizontal: SlidersHorizontal,
+ spinner: Loader2,
+ star: Star,
+ stop: Square,
+ table: Table,
+ layoutGrid: LayoutGrid,
+ verified: BadgeCheck,
+ video: Video,
+ circle: Circle
+} as const;
+
+export type AppIconName = keyof typeof APP_ICONS;
diff --git a/src/app/shared/ui/icon/icon.component.ts b/src/app/shared/ui/icon/icon.component.ts
new file mode 100644
index 0000000..3057de8
--- /dev/null
+++ b/src/app/shared/ui/icon/icon.component.ts
@@ -0,0 +1,39 @@
+import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
+import { LucideDynamicIcon } from '@lucide/angular';
+import { APP_ICONS, type AppIconName } from './icon-registry';
+
+/**
+ * Single entry point for every icon in the app. Renders a Lucide icon by
+ * canonical name (see icon-registry.ts) so every screen uses one icon family
+ * at one consistent size/stroke-width by default.
+ *
+ * Decorative by default (aria-hidden). Pass `ariaLabel` only when the icon
+ * itself carries meaning with no adjacent text (e.g. inside an icon-only
+ * button) - the label belongs on the interactive element when there is one.
+ */
+@Component({
+ selector: 'app-icon',
+ standalone: true,
+ imports: [LucideDynamicIcon],
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: {
+ class: 'app-icon',
+ '[style.display]': "'inline-flex'"
+ }
+})
+export class IconComponent {
+ readonly name = input.required();
+ readonly size = input(20);
+ readonly strokeWidth = input(2);
+ readonly ariaLabel = input(null);
+
+ protected readonly iconData = computed(() => APP_ICONS[this.name()]);
+}