EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
IconContext.tsx
Go to the documentation of this file.
1"use client";
2
3import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
4
5export type IconStyle = 'material-icons' | 'material-symbols-outlined' | 'material-symbols-rounded' | 'material-symbols-sharp' | 'emoji' | 'lucide';
6
7export interface IconTheme {
8 id: string;
9 name: string;
10 description: string;
11 style: IconStyle;
12 fontFamily: string;
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
17}
18
19export const iconThemes: Record<string, IconTheme> = {
20 'material-filled': {
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',
26 },
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',
33 weight: 400,
34 grade: 0,
35 opticalSize: 24,
36 fill: false,
37 },
38 'material-rounded': {
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',
44 weight: 400,
45 grade: 0,
46 opticalSize: 24,
47 fill: false,
48 },
49 'material-sharp': {
50 id: 'material-sharp',
51 name: 'Material Symbols (Sharp)',
52 description: 'Sharp corners for a bold, modern appearance',
53 style: 'material-symbols-sharp',
54 fontFamily: 'Material Symbols Sharp',
55 weight: 400,
56 grade: 0,
57 opticalSize: 24,
58 fill: false,
59 },
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',
66 weight: 400,
67 grade: 0,
68 opticalSize: 24,
69 fill: true,
70 },
71 'emoji': {
72 id: 'emoji',
73 name: 'Emoji Icons',
74 description: 'Colorful emoji-based icons for a playful look',
75 style: 'emoji',
76 fontFamily: 'system-ui',
77 },
78};
79
80interface IconContextType {
81 iconTheme: IconTheme;
82 iconThemeId: string;
83 setIconTheme: (themeId: string) => void;
84 getIconClass: (iconName: string) => string;
85 getIconStyle: () => React.CSSProperties;
86}
87
88const IconContext = createContext<IconContextType | undefined>(undefined);
89
90export function IconProvider({ children }: { children: ReactNode }) {
91 const [iconThemeId, setIconThemeId] = useState<string>('material-outlined');
92 const [iconTheme, setIconThemeState] = useState<IconTheme>(iconThemes['material-outlined']);
93
94 // Load saved icon theme from localStorage on mount
95 useEffect(() => {
96 const savedIconTheme = localStorage.getItem('iconTheme');
97 if (savedIconTheme && iconThemes[savedIconTheme]) {
98 setIconThemeId(savedIconTheme);
99 setIconThemeState(iconThemes[savedIconTheme]);
100 applyIconTheme(iconThemes[savedIconTheme]);
101 } else {
102 applyIconTheme(iconThemes['material-outlined']);
103 }
104 }, []);
105
106 const applyIconTheme = (theme: IconTheme) => {
107 const root = document.documentElement;
108
109 // Set CSS variables for icon styling
110 root.style.setProperty('--icon-font-family', theme.fontFamily);
111
112 if (theme.weight !== undefined) {
113 root.style.setProperty('--icon-font-weight', theme.weight.toString());
114 }
115
116 if (theme.grade !== undefined) {
117 root.style.setProperty('--icon-grade', theme.grade.toString());
118 }
119
120 if (theme.opticalSize !== undefined) {
121 root.style.setProperty('--icon-optical-size', theme.opticalSize.toString());
122 }
123
124 if (theme.fill !== undefined) {
125 root.style.setProperty('--icon-fill', theme.fill ? '1' : '0');
126 }
127
128 // Update the class on the html element
129 root.setAttribute('data-icon-style', theme.style);
130 };
131
132 const setIconTheme = (themeId: string) => {
133 if (!iconThemes[themeId]) {
134 console.error(`Icon theme "${themeId}" not found`);
135 return;
136 }
137
138 const theme = iconThemes[themeId];
139 setIconThemeId(themeId);
140 setIconThemeState(theme);
141 applyIconTheme(theme);
142 localStorage.setItem('iconTheme', themeId);
143 };
144
145 const getIconClass = (iconName: string): string => {
146 if (iconTheme.style === 'emoji') {
147 return 'icon-emoji';
148 }
149 return iconTheme.style;
150 };
151
152 const getIconStyle = (): React.CSSProperties => {
153 const style: React.CSSProperties = {
154 fontFamily: iconTheme.fontFamily,
155 };
156
157 if (iconTheme.weight !== undefined) {
158 style.fontWeight = iconTheme.weight;
159 }
160
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}`;
163 }
164
165 return style;
166 };
167
168 return (
169 <IconContext.Provider
170 value={{
171 iconTheme,
172 iconThemeId,
173 setIconTheme,
174 getIconClass,
175 getIconStyle,
176 }}
177 >
178 {children}
179 </IconContext.Provider>
180 );
181}
182
183export function useIcon() {
184 const context = useContext(IconContext);
185 if (!context) {
186 throw new Error('useIcon must be used within an IconProvider');
187 }
188 return context;
189}
190
191// Helper component for rendering icons
192interface IconProps {
193 name: string;
194 className?: string;
195 style?: React.CSSProperties;
196 size?: number;
197}
198
199export function Icon({ name, className = '', style = {}, size = 24 }: IconProps) {
200 const { iconTheme, getIconClass, getIconStyle } = useIcon();
201
202 // Emoji mapping for common icons
203 const emojiMap: Record<string, string> = {
204 // Navigation & Actions
205 home: '🏠',
206 dashboard: 'πŸ“Š',
207 menu: '☰',
208 arrow_back: '←',
209 arrow_forward: 'β†’',
210 arrow_upward: '↑',
211 arrow_downward: '↓',
212 expand_more: 'β–Ό',
213 expand_less: 'β–²',
214 chevron_right: 'β€Ί',
215 chevron_left: 'β€Ή',
216 navigation: '🧭',
217 rocket_launch: 'πŸš€',
218
219 // Account & User
220 person: 'πŸ‘€',
221 account_circle: 'πŸ‘€',
222 badge: 'πŸ‘₯',
223 group: 'πŸ‘₯',
224 groups: 'πŸ‘₯',
225 supervisor_account: 'πŸ‘₯',
226 admin_panel_settings: 'βš™οΈ',
227 waving_hand: 'πŸ‘‹',
228
229 // Shopping & Commerce
230 shopping_cart: 'πŸ›’',
231 shopping_bag: 'πŸ›οΈ',
232 point_of_sale: 'πŸ’°',
233 sell: 'πŸ’°',
234 store: 'πŸͺ',
235 storefront: '🏬',
236 business: '🏒',
237 payments: 'πŸ’³',
238
239 // Products & Inventory
240 inventory: 'πŸ“¦',
241 inventory_2: 'πŸ“¦',
242 category: '🏷️',
243 local_offer: '🏷️',
244 label: '🏷️',
245 barcode: 'β–ͺ️',
246 qr_code: 'β–ͺ️',
247 fact_check: 'βœ…',
248
249 // Reports & Analytics
250 assessment: 'πŸ“ˆ',
251 analytics: 'πŸ“Š',
252 bar_chart: 'πŸ“Š',
253 pie_chart: 'πŸ“‰',
254 trending_up: 'πŸ“ˆ',
255 trending_down: 'πŸ“‰',
256 insights: 'πŸ’‘',
257 lightbulb: 'πŸ’‘',
258 tips_and_updates: 'πŸ’‘',
259
260 // Communication
261 email: 'βœ‰οΈ',
262 mail: 'βœ‰οΈ',
263 phone: 'πŸ“ž',
264 call: 'πŸ“ž',
265 message: 'πŸ’¬',
266 chat: 'πŸ’¬',
267 sms: 'πŸ’¬',
268 campaign: 'πŸ“’',
269
270 // Files & Documents
271 description: 'πŸ“„',
272 receipt: '🧾',
273 receipt_long: '🧾',
274 folder: 'πŸ“',
275 folder_open: 'πŸ“‚',
276 attach_file: 'πŸ“Ž',
277 menu_book: 'πŸ“š',
278
279 // Actions
280 add: 'βž•',
281 add_circle: 'βž•',
282 remove: 'βž–',
283 edit: '✏️',
284 delete: 'πŸ—‘οΈ',
285 save: 'πŸ’Ύ',
286 print: 'πŸ–¨οΈ',
287 download: '⬇️',
288 upload: '⬆️',
289 refresh: 'πŸ”„',
290 sync: 'πŸ”„',
291 search: 'πŸ”',
292 filter_list: 'πŸ”',
293
294 // Status & Alerts
295 check: 'βœ“',
296 check_circle: 'βœ…',
297 close: 'βœ•',
298 cancel: '❌',
299 error: '❌',
300 warning: '⚠️',
301 info: 'ℹ️',
302 help: '❓',
303 notifications: 'πŸ””',
304 alarm: '⏰',
305 new_releases: 'πŸ†•',
306
307 // Settings & Tools
308 settings: 'βš™οΈ',
309 tune: 'βš™οΈ',
310 build: 'πŸ”§',
311 construction: 'πŸ”¨',
312 computer: 'πŸ’»',
313
314 // Security
315 lock: 'πŸ”’',
316 lock_open: 'πŸ”“',
317 unlock: 'πŸ”“',
318 visibility: 'πŸ‘οΈ',
319 visibility_off: 'πŸ™ˆ',
320 security: 'πŸ”',
321 vpn_key: 'πŸ”‘',
322
323 // Time & Calendar
324 calendar: 'πŸ“…',
325 calendar_month: 'πŸ“…',
326 calendar_today: 'πŸ“…',
327 schedule: 'πŸ•',
328 clock: 'πŸ•',
329 access_time: 'πŸ•',
330 timer: '⏱️',
331 history: 'πŸ•°οΈ',
332
333 // Location & Map
334 location_on: 'πŸ“',
335 place: 'πŸ“',
336 map: 'πŸ—ΊοΈ',
337 directions: '🧭',
338
339 // Media
340 image: 'πŸ–ΌοΈ',
341 photo: 'πŸ“·',
342 camera: 'πŸ“·',
343 video: 'πŸŽ₯',
344
345 // Finance
346 credit_card: 'πŸ’³',
347 payment: 'πŸ’³',
348 account_balance: '🏦',
349 attach_money: 'πŸ’΅',
350 card_giftcard: '🎁',
351
352 // Additional POS Help icons
353 keyboard_return: '↩️',
354 looks_one: '1️⃣',
355 looks_two: '2️⃣',
356 looks_3: '3️⃣',
357 phone_iphone: 'πŸ“±',
358
359 // Misc
360 favorite: '❀️',
361 star: '⭐',
362 bookmark: 'πŸ”–',
363 flag: '🚩',
364 logout: 'πŸšͺ',
365 login: 'πŸšͺ',
366 power_settings_new: '⏻',
367 };
368
369 if (iconTheme.style === 'emoji') {
370 return (
371 <span
372 className={`icon-emoji ${className}`}
373 role="img"
374 aria-label={name}
375 style={{
376 fontSize: `${size}px`,
377 lineHeight: 1,
378 display: 'inline-block',
379 fontFamily: 'Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, Android Emoji, EmojiSymbols, EmojiOne Color, sans-serif',
380 ...style,
381 }}
382 >
383 {emojiMap[name] || 'πŸ“¦'}
384 </span>
385 );
386 }
387
388 return (
389 <span
390 className={`${getIconClass(name)} ${className}`}
391 style={{
392 fontSize: `${size}px`,
393 ...getIconStyle(),
394 ...style,
395 }}
396 >
397 {name}
398 </span>
399 );
400}
401
402export function getIconThemeList() {
403 return Object.values(iconThemes).map((theme) => ({
404 id: theme.id,
405 name: theme.name,
406 description: theme.description,
407 }));
408}