| 1 |
/** |
| 2 |
* Centralized Icon System |
| 3 |
* |
| 4 |
* This file provides a centralized way to manage icons across both React and PHP. |
| 5 |
* Icons are defined in /includes/icons.json and loaded dynamically. |
| 6 |
*/ |
| 7 |
|
| 8 |
import { createElement } from "react"; |
| 9 |
|
| 10 |
// Import all Lucide icons we use |
| 11 |
import { |
| 12 |
Activity, |
| 13 |
UtensilsCrossed, |
| 14 |
Building2, |
| 15 |
Bus, |
| 16 |
Moon, |
| 17 |
Package, |
| 18 |
Target, |
| 19 |
Camera, |
| 20 |
Mountain, |
| 21 |
Waves, |
| 22 |
Palette, |
| 23 |
Plane, |
| 24 |
Car, |
| 25 |
Hotel, |
| 26 |
Coffee, |
| 27 |
Bed, |
| 28 |
MapPin, |
| 29 |
Footprints, |
| 30 |
Eye, |
| 31 |
Clock, |
| 32 |
Calendar, |
| 33 |
Image as ImageIcon, |
| 34 |
Music, |
| 35 |
Gamepad2, |
| 36 |
BookOpen, |
| 37 |
ShoppingBag, |
| 38 |
Heart, |
| 39 |
Star, |
| 40 |
Zap, |
| 41 |
Flame, |
| 42 |
FileText, |
| 43 |
Download, |
| 44 |
Users, |
| 45 |
Globe, |
| 46 |
HelpCircle, |
| 47 |
MessageCircle, |
| 48 |
Check, |
| 49 |
Info, |
| 50 |
} from "lucide-react"; |
| 51 |
|
| 52 |
// Map icon names to Lucide components |
| 53 |
const lucideIconMap: Record< |
| 54 |
string, |
| 55 |
React.ComponentType<React.SVGProps<SVGSVGElement>> |
| 56 |
> = { |
| 57 |
activity: Activity, |
| 58 |
footprints: Footprints, |
| 59 |
mountain: Mountain, |
| 60 |
waves: Waves, |
| 61 |
camera: Camera, |
| 62 |
eye: Eye, |
| 63 |
target: Target, |
| 64 |
zap: Zap, |
| 65 |
flame: Flame, |
| 66 |
utensils: UtensilsCrossed, |
| 67 |
coffee: Coffee, |
| 68 |
hotel: Hotel, |
| 69 |
bed: Bed, |
| 70 |
building: Building2, |
| 71 |
bus: Bus, |
| 72 |
plane: Plane, |
| 73 |
car: Car, |
| 74 |
"map-pin": MapPin, |
| 75 |
calendar: Calendar, |
| 76 |
clock: Clock, |
| 77 |
moon: Moon, |
| 78 |
package: Package, |
| 79 |
palette: Palette, |
| 80 |
image: ImageIcon, |
| 81 |
music: Music, |
| 82 |
gamepad: Gamepad2, |
| 83 |
book: BookOpen, |
| 84 |
shopping: ShoppingBag, |
| 85 |
heart: Heart, |
| 86 |
star: Star, |
| 87 |
"file-text": FileText, |
| 88 |
download: Download, |
| 89 |
users: Users, |
| 90 |
globe: Globe, |
| 91 |
"help-circle": HelpCircle, |
| 92 |
"message-circle": MessageCircle, |
| 93 |
check: Check, |
| 94 |
info: Info, |
| 95 |
}; |
| 96 |
|
| 97 |
export type IconName = keyof typeof lucideIconMap; |
| 98 |
|
| 99 |
export interface IconData { |
| 100 |
label: string; |
| 101 |
category: |
| 102 |
| "activity" |
| 103 |
| "travel" |
| 104 |
| "food" |
| 105 |
| "accommodation" |
| 106 |
| "transport" |
| 107 |
| "general"; |
| 108 |
svg: string; |
| 109 |
} |
| 110 |
|
| 111 |
export interface IconOption { |
| 112 |
name: IconName; |
| 113 |
label: string; |
| 114 |
component: React.ComponentType<React.SVGProps<SVGSVGElement>>; |
| 115 |
category: |
| 116 |
| "activity" |
| 117 |
| "travel" |
| 118 |
| "food" |
| 119 |
| "accommodation" |
| 120 |
| "transport" |
| 121 |
| "general"; |
| 122 |
} |
| 123 |
|
| 124 |
// Cache for loaded icons |
| 125 |
let iconsCache: Record<string, IconData> | null = null; |
| 126 |
|
| 127 |
/** |
| 128 |
* Load icons from the centralized JSON file |
| 129 |
*/ |
| 130 |
export async function loadIcons(): Promise<Record<string, IconData>> { |
| 131 |
if (iconsCache !== null) { |
| 132 |
return iconsCache; |
| 133 |
} |
| 134 |
|
| 135 |
try { |
| 136 |
// Load from the same JSON file that PHP uses |
| 137 |
const response = await fetch( |
| 138 |
"/wp-content/plugins/yatra/includes/icons.json", |
| 139 |
); |
| 140 |
if (!response.ok) { |
| 141 |
throw new Error("Failed to load icons"); |
| 142 |
} |
| 143 |
|
| 144 |
const loadedIcons = await response.json(); |
| 145 |
iconsCache = loadedIcons || {}; |
| 146 |
return iconsCache as Record<string, IconData>; |
| 147 |
} catch (error) { |
| 148 |
console.warn("Failed to load centralized icons, using fallback:", error); |
| 149 |
iconsCache = {}; |
| 150 |
return iconsCache; |
| 151 |
} |
| 152 |
} |
| 153 |
|
| 154 |
/** |
| 155 |
* Get all available icon options for the icon picker |
| 156 |
*/ |
| 157 |
export function getIconOptions(): IconOption[] { |
| 158 |
return Object.entries(lucideIconMap).map(([name, component]) => ({ |
| 159 |
name: name as IconName, |
| 160 |
label: name.charAt(0).toUpperCase() + name.slice(1).replace("-", " "), |
| 161 |
component, |
| 162 |
category: getCategoryForIcon(name as IconName), |
| 163 |
})); |
| 164 |
} |
| 165 |
|
| 166 |
/** |
| 167 |
* Get category for an icon (fallback when JSON is not loaded) |
| 168 |
*/ |
| 169 |
function getCategoryForIcon(iconName: IconName): IconOption["category"] { |
| 170 |
const categoryMap: Record<string, IconOption["category"]> = { |
| 171 |
activity: "activity", |
| 172 |
footprints: "activity", |
| 173 |
mountain: "activity", |
| 174 |
waves: "activity", |
| 175 |
camera: "activity", |
| 176 |
eye: "activity", |
| 177 |
target: "activity", |
| 178 |
zap: "activity", |
| 179 |
flame: "activity", |
| 180 |
utensils: "food", |
| 181 |
coffee: "food", |
| 182 |
hotel: "accommodation", |
| 183 |
bed: "accommodation", |
| 184 |
building: "accommodation", |
| 185 |
bus: "transport", |
| 186 |
plane: "transport", |
| 187 |
car: "transport", |
| 188 |
"map-pin": "travel", |
| 189 |
calendar: "travel", |
| 190 |
clock: "travel", |
| 191 |
moon: "general", |
| 192 |
package: "general", |
| 193 |
palette: "general", |
| 194 |
image: "general", |
| 195 |
music: "general", |
| 196 |
gamepad: "general", |
| 197 |
book: "general", |
| 198 |
shopping: "general", |
| 199 |
heart: "general", |
| 200 |
star: "general", |
| 201 |
"file-text": "general", |
| 202 |
download: "general", |
| 203 |
users: "general", |
| 204 |
globe: "general", |
| 205 |
"help-circle": "general", |
| 206 |
"message-circle": "general", |
| 207 |
check: "general", |
| 208 |
info: "general", |
| 209 |
}; |
| 210 |
|
| 211 |
return categoryMap[iconName] || "general"; |
| 212 |
} |
| 213 |
|
| 214 |
/** |
| 215 |
* Get React component for an icon name |
| 216 |
*/ |
| 217 |
export function getIconComponent( |
| 218 |
iconName: string, |
| 219 |
): React.ComponentType<React.SVGProps<SVGSVGElement>> | null { |
| 220 |
return lucideIconMap[iconName] || null; |
| 221 |
} |
| 222 |
|
| 223 |
/** |
| 224 |
* Render an icon by name |
| 225 |
*/ |
| 226 |
export function renderIcon( |
| 227 |
iconName: string, |
| 228 |
props: React.SVGProps<SVGSVGElement> = {}, |
| 229 |
): React.ReactElement | null { |
| 230 |
const IconComponent = getIconComponent(iconName); |
| 231 |
if (!IconComponent) { |
| 232 |
return null; |
| 233 |
} |
| 234 |
|
| 235 |
return createElement(IconComponent, props); |
| 236 |
} |
| 237 |
|
| 238 |
export default { |
| 239 |
loadIcons, |
| 240 |
getIconOptions, |
| 241 |
getIconComponent, |
| 242 |
renderIcon, |
| 243 |
}; |
| 244 |
|