PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | resources/js/lib/icons.ts +62 -209 3.0.2.6 → 3.0.16 View file →
@@ -1,239 +1,90 @@
1 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.
2 + * Yatra icon library (includes/icons.json, expanded via scripts/sync-lucide-icons-json.mjs)
3 + * + helpers for admin UI. Public PHP rendering uses yatra_svg_icon() / yatra_stored_picker_icon_markup().
6 4 */
7 5
8 6 import { createElement } from "react";
7 +import type { SVGProps } from "react";
8 +import yatraIconsManifest from "@yatra/icons";
9 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 {
10 +export interface YatraManifestEntry {
100 11 label: string;
101 - category:
102 - | "activity"
103 - | "travel"
104 - | "food"
105 - | "accommodation"
106 - | "transport"
107 - | "general";
12 + category: string;
108 13 svg: string;
109 14 }
110 15
111 -export interface IconOption {
112 - name: IconName;
16 +export type IconOption = {
17 + name: string;
113 18 label: string;
114 - component: React.ComponentType<React.SVGProps<SVGSVGElement>>;
115 - category:
116 - | "activity"
117 - | "travel"
118 - | "food"
119 - | "accommodation"
120 - | "transport"
121 - | "general";
122 -}
19 + category: string;
20 + svg: string;
21 +};
123 22
124 -// Cache for loaded icons
125 -let iconsCache: Record<string, IconData> | null = null;
23 +/** @deprecated All string slugs from manifest are valid */
24 +export type IconName = string;
126 25
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 - }
26 +export function getYatraIconManifest(): Record<string, YatraManifestEntry> {
27 + return yatraIconsManifest as Record<string, YatraManifestEntry>;
152 28 }
153 29
154 -/**
155 - * Get all available icon options for the icon picker
156 - */
157 30 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 - }));
31 + const m = yatraIconsManifest as Record<string, YatraManifestEntry>;
32 + // Some manifest entries only carry `svg` (sync script left them without
33 + // label/category). Without defensive defaults, IconPicker's search filter
34 + // calls `.toLowerCase()` on undefined and crashes the whole panel.
35 + // Derive a readable label from the slug ("hot-air-balloon" → "Hot Air
36 + // Balloon") and fall back to a "general" bucket for the category so the
37 + // search and category-filter UI still works.
38 + const slugToLabel = (slug: string): string =>
39 + slug
40 + .split(/[-_\s]+/)
41 + .filter(Boolean)
42 + .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
43 + .join(" ");
44 + return Object.entries(m).map(([name, meta]) => {
45 + const safeMeta = (meta || {}) as Partial<YatraManifestEntry>;
46 + return {
47 + name,
48 + label:
49 + typeof safeMeta.label === "string" && safeMeta.label.length > 0
50 + ? safeMeta.label
51 + : slugToLabel(name),
52 + category:
53 + typeof safeMeta.category === "string" && safeMeta.category.length > 0
54 + ? safeMeta.category
55 + : "general",
56 + svg: safeMeta.svg || "",
57 + };
58 + });
164 59 }
165 60
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";
61 +export function getYatraIconSvg(slug: string): string | null {
62 + const m = yatraIconsManifest as Record<string, YatraManifestEntry>;
63 + return m[slug]?.svg ?? null;
212 64 }
213 65
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;
66 +/** @deprecated Prefer getYatraIconSvg + inline SVG; kept for narrow compatibility */
67 +export function getIconComponent(): null {
68 + return null;
221 69 }
222 70
223 -/**
224 - * Render an icon by name
225 - */
226 71 export function renderIcon(
227 72 iconName: string,
228 - props: React.SVGProps<SVGSVGElement> = {},
229 -): React.ReactElement | null {
230 - const IconComponent = getIconComponent(iconName);
231 - if (!IconComponent) {
73 + props: SVGProps<HTMLSpanElement> = {},
74 +): ReturnType<typeof createElement> | null {
75 + const svg = getYatraIconSvg(iconName);
76 + if (!svg) {
232 77 return null;
233 78 }
79 + return createElement("span", {
80 + ...props,
81 + dangerouslySetInnerHTML: { __html: svg },
82 + });
83 +}
234 84
235 - return createElement(IconComponent, props);
85 +export async function loadIcons(): Promise<Record<string, YatraManifestEntry>> {
86 + return getYatraIconManifest();
236 87 }
237 88
238 89 export default {
239 90 loadIcons,
@@ -239,5 +90,7 @@
239 90 loadIcons,
240 91 getIconOptions,
241 92 getIconComponent,
242 93 renderIcon,
94 + getYatraIconSvg,
95 + getYatraIconManifest,
243 96 };