3import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
5export type IconStyle = 'material-icons' | 'material-symbols-outlined' | 'material-symbols-rounded' | 'material-symbols-sharp' | 'emoji' | 'lucide';
7export interface IconTheme {
13 weight?: number; // 100-700 for Material Symbols
14 grade?: number; // -25 to 200 for Material Symbols
15 opticalSize?: number; // 20, 24, 40, 48 for Material Symbols
16 fill?: boolean; // Fill style for Material Symbols
19export const iconThemes: Record<string, IconTheme> = {
21 id: 'material-filled',
22 name: 'Material Icons (Filled)',
23 description: 'Classic Google Material Design filled icons',
24 style: 'material-icons',
25 fontFamily: 'Material Icons',
27 'material-outlined': {
28 id: 'material-outlined',
29 name: 'Material Symbols (Outlined)',
30 description: 'Modern outlined style with customizable weight',
31 style: 'material-symbols-outlined',
32 fontFamily: 'Material Symbols Outlined',
39 id: 'material-rounded',
40 name: 'Material Symbols (Rounded)',
41 description: 'Friendly rounded edges for a softer look',
42 style: 'material-symbols-rounded',
43 fontFamily: 'Material Symbols Rounded',
51 name: 'Material Symbols (Sharp)',
52 description: 'Sharp corners for a bold, modern appearance',
53 style: 'material-symbols-sharp',
54 fontFamily: 'Material Symbols Sharp',
60 'material-rounded-filled': {
61 id: 'material-rounded-filled',
62 name: 'Material Symbols (Rounded Filled)',
63 description: 'Rounded icons with filled style',
64 style: 'material-symbols-rounded',
65 fontFamily: 'Material Symbols Rounded',
74 description: 'Colorful emoji-based icons for a playful look',
76 fontFamily: 'system-ui',
80interface IconContextType {
83 setIconTheme: (themeId: string) => void;
84 getIconClass: (iconName: string) => string;
85 getIconStyle: () => React.CSSProperties;
88const IconContext = createContext<IconContextType | undefined>(undefined);
90export function IconProvider({ children }: { children: ReactNode }) {
91 const [iconThemeId, setIconThemeId] = useState<string>('material-outlined');
92 const [iconTheme, setIconThemeState] = useState<IconTheme>(iconThemes['material-outlined']);
94 // Load saved icon theme from localStorage on mount
96 const savedIconTheme = localStorage.getItem('iconTheme');
97 if (savedIconTheme && iconThemes[savedIconTheme]) {
98 setIconThemeId(savedIconTheme);
99 setIconThemeState(iconThemes[savedIconTheme]);
100 applyIconTheme(iconThemes[savedIconTheme]);
102 applyIconTheme(iconThemes['material-outlined']);
106 const applyIconTheme = (theme: IconTheme) => {
107 const root = document.documentElement;
109 // Set CSS variables for icon styling
110 root.style.setProperty('--icon-font-family', theme.fontFamily);
112 if (theme.weight !== undefined) {
113 root.style.setProperty('--icon-font-weight', theme.weight.toString());
116 if (theme.grade !== undefined) {
117 root.style.setProperty('--icon-grade', theme.grade.toString());
120 if (theme.opticalSize !== undefined) {
121 root.style.setProperty('--icon-optical-size', theme.opticalSize.toString());
124 if (theme.fill !== undefined) {
125 root.style.setProperty('--icon-fill', theme.fill ? '1' : '0');
128 // Update the class on the html element
129 root.setAttribute('data-icon-style', theme.style);
132 const setIconTheme = (themeId: string) => {
133 if (!iconThemes[themeId]) {
134 console.error(`Icon theme "${themeId}" not found`);
138 const theme = iconThemes[themeId];
139 setIconThemeId(themeId);
140 setIconThemeState(theme);
141 applyIconTheme(theme);
142 localStorage.setItem('iconTheme', themeId);
145 const getIconClass = (iconName: string): string => {
146 if (iconTheme.style === 'emoji') {
149 return iconTheme.style;
152 const getIconStyle = (): React.CSSProperties => {
153 const style: React.CSSProperties = {
154 fontFamily: iconTheme.fontFamily,
157 if (iconTheme.weight !== undefined) {
158 style.fontWeight = iconTheme.weight;
161 if (iconTheme.style.startsWith('material-symbols')) {
162 style.fontVariationSettings = `'FILL' ${iconTheme.fill ? 1 : 0}, 'wght' ${iconTheme.weight || 400}, 'GRAD' ${iconTheme.grade || 0}, 'opsz' ${iconTheme.opticalSize || 24}`;
169 <IconContext.Provider
179 </IconContext.Provider>
183export function useIcon() {
184 const context = useContext(IconContext);
186 throw new Error('useIcon must be used within an IconProvider');
191// Helper component for rendering icons
195 style?: React.CSSProperties;
199export function Icon({ name, className = '', style = {}, size = 24 }: IconProps) {
200 const { iconTheme, getIconClass, getIconStyle } = useIcon();
202 // Emoji mapping for common icons
203 const emojiMap: Record<string, string> = {
204 // Navigation & Actions
209 arrow_forward: 'β',
211 arrow_downward: 'β',
214 chevron_right: 'βΊ',
217 rocket_launch: 'π',
221 account_circle: 'π€',
225 supervisor_account: 'π₯',
226 admin_panel_settings: 'βοΈ',
229 // Shopping & Commerce
230 shopping_cart: 'π',
231 shopping_bag: 'ποΈ',
232 point_of_sale: 'π°',
239 // Products & Inventory
243 local_offer: 'π·οΈ',
249 // Reports & Analytics
255 trending_down: 'π',
258 tips_and_updates: 'π‘',
273 receipt_long: 'π§Ύ',
303 notifications: 'π',
305 new_releases: 'π',
311 construction: 'π¨',
318 visibility: 'ποΈ',
319 visibility_off: 'π',
325 calendar_month: 'π
',
326 calendar_today: 'π
',
348 account_balance: 'π¦',
349 attach_money: 'π΅',
350 card_giftcard: 'π',
352 // Additional POS Help icons
353 keyboard_return: 'β©οΈ',
354 looks_one: '1οΈβ£',
355 looks_two: '2οΈβ£',
357 phone_iphone: 'π±',
366 power_settings_new: 'β»',
369 if (iconTheme.style === 'emoji') {
372 className={`icon-emoji ${className}`}
376 fontSize: `${size}px`,
378 display: 'inline-block',
379 fontFamily: 'Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, Android Emoji, EmojiSymbols, EmojiOne Color, sans-serif',
383 {emojiMap[name] || 'π¦'}
390 className={`${getIconClass(name)} ${className}`}
392 fontSize: `${size}px`,
402export function getIconThemeList() {
403 return Object.values(iconThemes).map((theme) => ({
406 description: theme.description,