@deneb-ui/ui 2.0.111 → 2.0.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ /**
2
+ * EditableIcon — DENEB UI Framework
3
+ *
4
+ * Universal, developer-friendly editable vector icon component for all templates
5
+ * (Automotive, Gym, Restaurant, Bookshop, Real Estate, E-Commerce, etc.).
6
+ *
7
+ * Core capabilities:
8
+ * 1. Dynamic name-to-SVG resolution for 40+ standard business glyphs (speedometer, fuel,
9
+ * transmission, scale/compare, heart/saved, whatsapp, calculator, check, dumbbell, etc.).
10
+ * 2. Visual Editor Integration: When `fieldPath` is provided, attaches `data-preview-field-path`,
11
+ * `data-preview-control="icon-picker"`, and `data-preview-icon={name}` so Deneb Studio opens
12
+ * the Icon Picker modal.
13
+ * 3. Supports Lucide kebab-case ("file-check-2"), camelCase ("fileCheck2"), and PascalCase ("FileCheck2").
14
+ * 4. Extensible via `iconMap` or `customIcon` slot for template-specific graphics.
15
+ * 5. Zero-dependency: Renders crisp, scalable SVGs directly with zero layout shift.
16
+ *
17
+ * Created by Chamika Gayashan & Induranga Kawishwara
18
+ */
19
+ import React from 'react';
20
+ export interface EditableIconProps extends React.SVGAttributes<SVGSVGElement> {
21
+ /**
22
+ * The name of the icon (e.g. "gauge", "fuel", "settings", "scale", "heart", "whatsapp",
23
+ * "calculator", "file-check", "dumbbell", "book", "utensils", "clock", "pin", "user").
24
+ */
25
+ name?: string;
26
+ /**
27
+ * Field path in site-data.json (e.g. "cards.vehicles[0].specs.mileage.icon" or "home.benefits[0].icon").
28
+ * When supplied, marks this element as an editable icon in Deneb Studio.
29
+ */
30
+ fieldPath?: string;
31
+ /**
32
+ * Size of the icon in pixels (number) or CSS string (e.g. 16, 20, 24, "1rem", "1.25rem").
33
+ * Defaults to 16.
34
+ */
35
+ size?: number | string;
36
+ /**
37
+ * Stroke width for outline icons. Defaults to 2.
38
+ */
39
+ strokeWidth?: number | string;
40
+ /**
41
+ * Optional custom icon node to render instead of built-in glyphs.
42
+ */
43
+ customIcon?: React.ReactNode;
44
+ /**
45
+ * Fallback icon name if the given name is not found in the icon dictionary. Defaults to 'circle'.
46
+ */
47
+ fallback?: string;
48
+ /**
49
+ * Optional title for accessibility / tooltip.
50
+ */
51
+ title?: string;
52
+ /**
53
+ * Dictionary of custom SVG paths or renderers to merge with standard icons.
54
+ */
55
+ iconMap?: Record<string, React.ReactNode>;
56
+ /**
57
+ * Additional CSS class name.
58
+ */
59
+ className?: string;
60
+ /**
61
+ * Inline styles.
62
+ */
63
+ style?: React.CSSProperties;
64
+ }
65
+ export declare const EditableIcon: React.FC<EditableIconProps>;
66
+ export declare const DynamicIcon: React.FC<EditableIconProps>;
67
+ export declare const Icon: React.FC<EditableIconProps>;
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Icon = exports.DynamicIcon = exports.EditableIcon = void 0;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const SiteDataProvider_1 = require("./SiteDataProvider");
7
+ const BUILTIN_ICONS = {
8
+ // Vehicle & Specs
9
+ gauge: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m12 14 4-4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M3.34 19a10 10 0 1 1 17.32 0", strokeWidth: strokeWidth })] })),
10
+ fuel: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("line", { x1: "3", x2: "15", y1: "22", y2: "22", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "4", x2: "14", y1: "9", y2: "9", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M14 22V4a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v18", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M14 13h2a2 2 0 0 1 2 2v2a2 2 0 0 0 2 2a2 2 0 0 0 2-2V9.83a2 2 0 0 0-.59-1.42L18 5", strokeWidth: strokeWidth })] })),
11
+ settings: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "3", strokeWidth: strokeWidth })] })),
12
+ // Action, Social & Utility
13
+ scale: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M7 21h10", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 3v18", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2", strokeWidth: strokeWidth })] })),
14
+ whatsapp: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M3 21l1.65-3.8a9 9 0 1 1 3.4 2.9L3 21", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M9 10a.5.5 0 0 0 1 0V9a.5.5 0 0 0-1 0v1a5 5 0 0 0 5 5h1a.5.5 0 0 0 0-1h-1a.5.5 0 0 0 0 1", strokeWidth: strokeWidth })] })),
15
+ heart: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z", strokeWidth: strokeWidth })),
16
+ calculator: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { width: "16", height: "20", x: "4", y: "2", rx: "2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "16", y1: "6", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "16", x2: "16", y1: "14", y2: "18", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M16 10h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 10h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M8 10h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 14h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M8 14h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 18h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M8 18h.01", strokeWidth: strokeWidth })] })),
17
+ 'file-check': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polyline", { points: "14 2 14 8 20 8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m3 15 2 2 4-4", strokeWidth: strokeWidth })] })),
18
+ 'file-check-2': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polyline", { points: "14 2 14 8 20 8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m3 15 2 2 4-4", strokeWidth: strokeWidth })] })),
19
+ 'message-circle': ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z", strokeWidth: strokeWidth })),
20
+ 'message-square': ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", strokeWidth: strokeWidth })),
21
+ 'search-check': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("circle", { cx: "11", cy: "11", r: "8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m21 21-4.3-4.3", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m8 11 2 2 4-4", strokeWidth: strokeWidth })] })),
22
+ search: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("circle", { cx: "11", cy: "11", r: "8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m21 21-4.3-4.3", strokeWidth: strokeWidth })] })),
23
+ // Fitness / Gym
24
+ dumbbell: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m6.5 6.5 11 11", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m21 21-1-1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m3 3 1 1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m18 22 4-4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m2 6 4-4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m3 10 7-7", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m14 21 7-7", strokeWidth: strokeWidth })] })),
25
+ 'heart-pulse': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M3.22 12H9.5l1.5-3 2 6 1.5-3h4.78", strokeWidth: strokeWidth })] })),
26
+ timer: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("line", { x1: "10", x2: "14", y1: "2", y2: "2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "12", x2: "15", y1: "14", y2: "11", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "14", r: "8", strokeWidth: strokeWidth })] })),
27
+ // Bookshop / Education
28
+ book: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z", strokeWidth: strokeWidth })] })),
29
+ 'book-open': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z", strokeWidth: strokeWidth })] })),
30
+ 'pen-tool': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m12 19 7-7 3 3-7 7-3-3z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m18 13-1.5-7.5L2 2l3.5 14.5L13 18l5-5z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m2 2 7.586 7.586", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "11", cy: "11", r: "2", strokeWidth: strokeWidth })] })),
31
+ // Restaurant / Food
32
+ utensils: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M18 2v6a3 3 0 0 1-3 3 3 3 0 0 1-3-3V2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M15 11v11", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M5 2v14a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2V2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M7 2v5", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M21 15V2a5 5 0 0 0-5 5v3a2 2 0 0 0 2 2h3Z", strokeWidth: strokeWidth })] })),
33
+ coffee: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M10 2v2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M14 2v2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h12Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M6 2v2", strokeWidth: strokeWidth })] })),
34
+ // Florist / Botanical / Eco
35
+ flower: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "3", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 16.5A4.5 4.5 0 1 1 7.5 12 4.5 4.5 0 1 1 12 7.5a4.5 4.5 0 1 1 4.5 4.5 4.5 4.5 0 1 1-4.5 4.5", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M8 12H4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 8V4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M16 12h4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 16v4", strokeWidth: strokeWidth })] })),
36
+ 'flower-2': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M12 5a3 3 0 1 1 3 3m-3-3a3 3 0 1 0-3 3m3-3v1m3 2a3 3 0 1 1 3 3m-3-3a3 3 0 1 0-3 3m3-3h-1m-2 3a3 3 0 1 1-3 3m3-3a3 3 0 1 0-3-3m3 3v-1m-3-2a3 3 0 1 1-3-3m3 3a3 3 0 1 0 3-3m-3 3h1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "2", strokeWidth: strokeWidth })] })),
37
+ leaf: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12", strokeWidth: strokeWidth })] })),
38
+ sprout: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M7 20h10", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M10 20c5.5-2.5.8-6.4 3-10", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M9.5 9.4c1.1.8 1.8 2.2 2.3 3.7-2 .4-3.5.4-4.8-.3-1.2-.6-2.3-1.9-3-4.2 2.8-.5 4.4 0 5.5.8z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M14.1 6a7 7 0 0 0-1.1 4c1.9-.1 3.3-.6 4.3-1.4 1-1 1.6-2.3 1.7-4.6-2.7.1-4 1-4.9 2z", strokeWidth: strokeWidth })] })),
39
+ wallet: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4", strokeWidth: strokeWidth })] })),
40
+ 'refresh-cw': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M21 3v5h-5", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M8 16H3v5", strokeWidth: strokeWidth })] })),
41
+ navigation: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("polygon", { points: "3 11 22 2 13 21 11 13 3 11", strokeWidth: strokeWidth })),
42
+ 'arrow-up-right': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("line", { x1: "7", x2: "17", y1: "17", y2: "7", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polyline", { points: "7 7 17 7 17 17", strokeWidth: strokeWidth })] })),
43
+ // Common Business & UI
44
+ clock: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "10", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polyline", { points: "12 6 12 12 16 14", strokeWidth: strokeWidth })] })),
45
+ 'map-pin': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "10", r: "3", strokeWidth: strokeWidth })] })),
46
+ phone: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z", strokeWidth: strokeWidth })),
47
+ mail: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7", strokeWidth: strokeWidth })] })),
48
+ calendar: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "16", x2: "16", y1: "2", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "8", y1: "2", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "3", x2: "21", y1: "10", y2: "10", strokeWidth: strokeWidth })] })),
49
+ user: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "7", r: "4", strokeWidth: strokeWidth })] })),
50
+ users: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "9", cy: "7", r: "4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M16 3.13a4 4 0 0 1 0 7.75", strokeWidth: strokeWidth })] })),
51
+ check: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("polyline", { points: "20 6 9 17 4 12", strokeWidth: strokeWidth })),
52
+ 'check-circle': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polyline", { points: "22 4 12 14.01 9 11.01", strokeWidth: strokeWidth })] })),
53
+ star: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2", strokeWidth: strokeWidth })),
54
+ shield: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z", strokeWidth: strokeWidth })),
55
+ zap: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("polygon", { points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2", strokeWidth: strokeWidth })),
56
+ sparkles: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M5 3v4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M19 17v4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M3 5h4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M17 19h4", strokeWidth: strokeWidth })] })),
57
+ award: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "8", r: "7", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polyline", { points: "8.21 13.89 7 23 12 20 17 23 15.79 13.88", strokeWidth: strokeWidth })] })),
58
+ circle: ({ strokeWidth }) => ((0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "8", strokeWidth: strokeWidth })),
59
+ car: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m21 8-2 2-1.5-3.7A2 2 0 0 0 15.64 5H8.36a2 2 0 0 0-1.86 1.3L5 10l-2-2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M7 14h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M17 14h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("rect", { width: "18", height: "8", x: "3", y: "10", rx: "2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M5 18v2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M19 18v2", strokeWidth: strokeWidth })] })),
60
+ camera: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "13", r: "3", strokeWidth: strokeWidth })] })),
61
+ handshake: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m11 17 2 2a1 1 0 0 0 1.4 0l4.6-4.6a2 2 0 0 0 0-2.8l-3.2-3.2a2 2 0 0 0-2.8 0L11 10.4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m18 11 3-3a2 2 0 0 0 0-2.8l-1.2-1.2a2 2 0 0 0-2.8 0L13.4 7.6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m2 11 3-3a2 2 0 0 1 2.8 0l1.2 1.2a2 2 0 0 1 0 2.8L5.4 15.6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m13 17-2 2a1 1 0 0 1-1.4 0L5 14.4a2 2 0 0 1 0-2.8l3.2-3.2a2 2 0 0 1 2.8 0L13 10.4", strokeWidth: strokeWidth })] })),
62
+ eye: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "3", strokeWidth: strokeWidth })] })),
63
+ 'file-search': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v3", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polyline", { points: "14 2 14 8 20 8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("circle", { cx: "11.5", cy: "14.5", r: "3.5", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m14 17 2 2", strokeWidth: strokeWidth })] })),
64
+ 'file-text': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M10 9H8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M16 13H8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M16 17H8", strokeWidth: strokeWidth })] })),
65
+ 'clipboard-list': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 11h4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 16h4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M8 11h.01", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M8 16h.01", strokeWidth: strokeWidth })] })),
66
+ bell: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0", strokeWidth: strokeWidth })] })),
67
+ landmark: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("line", { x1: "3", x2: "21", y1: "22", y2: "22", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "6", x2: "6", y1: "18", y2: "11", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "10", x2: "10", y1: "18", y2: "11", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "14", x2: "14", y1: "18", y2: "11", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "18", x2: "18", y1: "18", y2: "11", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("polygon", { points: "12 2 20 7 4 7", strokeWidth: strokeWidth })] })),
68
+ 'calendar-check': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "16", x2: "16", y1: "2", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "8", y1: "2", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "3", x2: "21", y1: "10", y2: "10", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m9 16 2 2 4-4", strokeWidth: strokeWidth })] })),
69
+ trophy: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M6 9H4.5a2.5 2.5 0 0 1 0-5H6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M18 9h1.5a2.5 2.5 0 0 0 0-5H18", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M4 22h16", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M18 2H6v7a6 6 0 0 0 12 0V2Z", strokeWidth: strokeWidth })] })),
70
+ wand: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "m15 4-2 2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m15 2 2 2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m12 7 2-2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M9 11 3 17l4 4 6-6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "m19 9-2 2", strokeWidth: strokeWidth })] })),
71
+ copy: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", strokeWidth: strokeWidth })] })),
72
+ info: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "10", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 16v-4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M12 8h.01", strokeWidth: strokeWidth })] })),
73
+ 'rotate-ccw': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("path", { d: "M3 3v5h5", strokeWidth: strokeWidth })] })),
74
+ 'sliders-horizontal': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("line", { x1: "21", x2: "14", y1: "4", y2: "4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "10", x2: "3", y1: "4", y2: "4", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "21", x2: "12", y1: "12", y2: "12", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "3", y1: "12", y2: "12", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "21", x2: "16", y1: "20", y2: "20", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "12", x2: "3", y1: "20", y2: "20", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "14", x2: "14", y1: "2", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "8", y1: "10", y2: "14", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "16", x2: "16", y1: "18", y2: "22", strokeWidth: strokeWidth })] })),
75
+ 'layout-grid': ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1", strokeWidth: strokeWidth })] })),
76
+ list: ({ strokeWidth }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "21", y1: "6", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "21", y1: "12", y2: "12", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "8", x2: "21", y1: "18", y2: "18", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "3", x2: "3.01", y1: "6", y2: "6", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "3", x2: "3.01", y1: "12", y2: "12", strokeWidth: strokeWidth }), (0, jsx_runtime_1.jsx)("line", { x1: "3", x2: "3.01", y1: "18", y2: "18", strokeWidth: strokeWidth })] })),
77
+ };
78
+ // ── Aliases & Normalization ───────────────────────────────────────────────────
79
+ const ICON_ALIASES = {
80
+ speedometer: 'gauge',
81
+ odometer: 'gauge',
82
+ mileage: 'gauge',
83
+ gas: 'fuel',
84
+ petrol: 'fuel',
85
+ diesel: 'fuel',
86
+ electric: 'zap',
87
+ battery: 'zap',
88
+ power: 'zap',
89
+ transmission: 'settings',
90
+ gear: 'settings',
91
+ gearbox: 'settings',
92
+ cog: 'settings',
93
+ compare: 'scale',
94
+ balance: 'scale',
95
+ scales: 'scale',
96
+ saved: 'heart',
97
+ favorite: 'heart',
98
+ like: 'heart',
99
+ document: 'file-check',
100
+ evidence: 'file-check',
101
+ report: 'file-check',
102
+ chat: 'message-circle',
103
+ message: 'message-circle',
104
+ comments: 'message-square',
105
+ calc: 'calculator',
106
+ loan: 'calculator',
107
+ finance: 'calculator',
108
+ sourcing: 'search-check',
109
+ verified: 'check-circle',
110
+ gym: 'dumbbell',
111
+ fitness: 'dumbbell',
112
+ weight: 'dumbbell',
113
+ menu: 'utensils',
114
+ dish: 'utensils',
115
+ food: 'utensils',
116
+ restaurant: 'utensils',
117
+ duration: 'clock',
118
+ time: 'clock',
119
+ location: 'map-pin',
120
+ pin: 'map-pin',
121
+ address: 'map-pin',
122
+ call: 'phone',
123
+ telephone: 'phone',
124
+ email: 'mail',
125
+ directions: 'navigation',
126
+ navigate: 'navigation',
127
+ compass: 'navigation',
128
+ money: 'wallet',
129
+ wallet: 'wallet',
130
+ ownership: 'wallet',
131
+ cycle: 'refresh-cw',
132
+ refresh: 'refresh-cw',
133
+ 'trade-in': 'refresh-cw',
134
+ arrow: 'arrow-up-right',
135
+ external: 'arrow-up-right',
136
+ 'up-right': 'arrow-up-right',
137
+ rose: 'flower',
138
+ tulip: 'flower',
139
+ bloom: 'flower',
140
+ blossom: 'flower',
141
+ floral: 'flower',
142
+ flowers: 'flower',
143
+ plant: 'sprout',
144
+ seedling: 'sprout',
145
+ nature: 'leaf',
146
+ eco: 'leaf',
147
+ tree: 'leaf',
148
+ 'car-front': 'car',
149
+ auto: 'car',
150
+ automobile: 'car',
151
+ vehicle: 'car',
152
+ 'wand-2': 'wand',
153
+ magic: 'wand',
154
+ grid: 'layout-grid',
155
+ table: 'layout-grid',
156
+ sliders: 'sliders-horizontal',
157
+ filters: 'sliders-horizontal',
158
+ bank: 'landmark',
159
+ alert: 'bell',
160
+ notification: 'bell',
161
+ 'plug-zap': 'zap',
162
+ ev: 'zap',
163
+ hybrid: 'zap',
164
+ };
165
+ function normalizeIconName(name) {
166
+ if (!name)
167
+ return 'circle';
168
+ // Strip "Icon" suffix e.g. "GaugeIcon" -> "gauge"
169
+ let clean = name.replace(/Icon$/i, '');
170
+ // Convert PascalCase or camelCase to kebab-case
171
+ clean = clean
172
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
173
+ .toLowerCase()
174
+ .trim();
175
+ if (ICON_ALIASES[clean]) {
176
+ return ICON_ALIASES[clean];
177
+ }
178
+ return clean;
179
+ }
180
+ function resolveFieldFromPath(obj, pathStr) {
181
+ if (!obj || !pathStr)
182
+ return undefined;
183
+ const parts = pathStr.replace(/\[(\d+)\]/g, '.$1').split('.').filter(Boolean);
184
+ let cur = obj;
185
+ for (const p of parts) {
186
+ if (cur == null)
187
+ return undefined;
188
+ cur = cur[p];
189
+ }
190
+ return cur;
191
+ }
192
+ // ── Component Implementation ──────────────────────────────────────────────────
193
+ const EditableIcon = ({ name = 'circle', fieldPath, size = 16, strokeWidth = 2, customIcon, fallback = 'circle', title, iconMap, className = '', style = {}, ...svgProps }) => {
194
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
195
+ const content = siteData?.content || siteData;
196
+ // If a fieldPath is provided, check if the data store overrides the icon name string
197
+ const resolvedNameFromStore = fieldPath
198
+ ? (resolveFieldFromPath(content, fieldPath) ?? resolveFieldFromPath(siteData, fieldPath))
199
+ : undefined;
200
+ const activeName = (typeof resolvedNameFromStore === 'string' && resolvedNameFromStore.trim())
201
+ ? resolvedNameFromStore.trim()
202
+ : name;
203
+ const normalized = normalizeIconName(activeName);
204
+ // 1. Custom icon slot
205
+ if (customIcon) {
206
+ return ((0, jsx_runtime_1.jsx)("span", { className: `deneb-editable-icon ${className}`, style: {
207
+ display: 'inline-flex',
208
+ alignItems: 'center',
209
+ justifyContent: 'center',
210
+ lineHeight: 1,
211
+ ...style,
212
+ }, ...(fieldPath ? {
213
+ 'data-preview-field-path': fieldPath,
214
+ 'data-preview-control': 'icon-picker',
215
+ 'data-preview-icon-type': 'lucide',
216
+ 'data-preview-icon': activeName,
217
+ } : {}), children: customIcon }));
218
+ }
219
+ // 2. Custom dictionary override
220
+ if (iconMap && iconMap[normalized]) {
221
+ return ((0, jsx_runtime_1.jsx)("span", { className: `deneb-editable-icon ${className}`, style: {
222
+ display: 'inline-flex',
223
+ alignItems: 'center',
224
+ justifyContent: 'center',
225
+ lineHeight: 1,
226
+ ...style,
227
+ }, ...(fieldPath ? {
228
+ 'data-preview-field-path': fieldPath,
229
+ 'data-preview-control': 'icon-picker',
230
+ 'data-preview-icon-type': 'lucide',
231
+ 'data-preview-icon': activeName,
232
+ } : {}), children: iconMap[normalized] }));
233
+ }
234
+ // 3. Built-in SVG lookup
235
+ const renderer = BUILTIN_ICONS[normalized] || BUILTIN_ICONS[fallback] || BUILTIN_ICONS.circle;
236
+ return ((0, jsx_runtime_1.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round", width: size, height: size, className: `deneb-editable-icon ${className}`, style: {
237
+ display: 'inline-block',
238
+ verticalAlign: 'middle',
239
+ flexShrink: 0,
240
+ ...style,
241
+ }, "aria-hidden": !title, role: title ? 'img' : undefined, ...(fieldPath ? {
242
+ 'data-preview-field-path': fieldPath,
243
+ 'data-preview-control': 'icon-picker',
244
+ 'data-preview-icon-type': 'lucide',
245
+ 'data-preview-icon': activeName,
246
+ } : {}), ...svgProps, children: [title && (0, jsx_runtime_1.jsx)("title", { children: title }), renderer({ strokeWidth })] }));
247
+ };
248
+ exports.EditableIcon = EditableIcon;
249
+ // Aliases for developer convenience
250
+ exports.DynamicIcon = exports.EditableIcon;
251
+ exports.Icon = exports.EditableIcon;
@@ -156,13 +156,15 @@ function EditableProductCard({ itemPath, product: productProp, item: itemProp, c
156
156
  const originalPriceKey = 'compareAtPrice' in (product || {}) && !('originalPrice' in (product || {}) && product.originalPrice !== undefined)
157
157
  ? 'compareAtPrice'
158
158
  : 'originalPrice';
159
- const descriptionKey = 'desc' in (product || {}) && !('description' in (product || {}) && product.description)
160
- ? 'desc'
161
- : 'description';
162
- const description = String(product?.description || product?.desc || product?.details || '');
159
+ const descriptionKey = ['details', 'description', 'desc'].find((key) => product && key in product && product[key]) ||
160
+ ['details', 'description', 'desc'].find((key) => product && key in product) ||
161
+ 'details';
162
+ const description = String(product?.[descriptionKey] || product?.description || product?.desc || product?.details || '');
163
+ const hasDescriptionField = Boolean(product && ('details' in product || 'description' in product || 'desc' in product));
163
164
  const category = String(product?.category || '');
164
165
  const badgeKey = 'tag' in (product || {}) && !('badge' in (product || {}) && product.badge) ? 'tag' : 'badge';
165
166
  const badge = String(product?.badge || product?.tag || '');
167
+ const hasBadgeField = Boolean(product && ('badge' in product || 'tag' in product));
166
168
  const fallbackImage = imageFallback || 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?auto=format&fit=crop&w=800&q=80';
167
169
  const rawImage = product?.imageUrl ||
168
170
  product?.image ||
@@ -321,7 +323,20 @@ function EditableProductCard({ itemPath, product: productProp, item: itemProp, c
321
323
  if (onDetailClick) {
322
324
  onDetailClick(product, resolvedDetailHref);
323
325
  }
324
- }, "data-preview-static": "Open the stable live product detail page", style: { display: 'block', width: '100%', height: '100%', textDecoration: 'none' }, title: `View details for ${name}`, children: (0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${itemPath}.${imageKey}`, "data-preview-field-path": `${itemPath}.${imageKey}`, src: currentImage, fallbackSrc: imageFallback, alt: name, aspectRatio: isHorizontal ? 'square' : '4/3', fit: "cover", hoverZoom: true, style: { width: '100%', height: '100%', display: 'block' } }) })) : ((0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${itemPath}.${imageKey}`, "data-preview-field-path": `${itemPath}.${imageKey}`, src: currentImage, fallbackSrc: imageFallback, alt: name, aspectRatio: isHorizontal ? 'square' : '4/3', fit: "cover", hoverZoom: true, style: { width: '100%', height: '100%', display: 'block' } })), badge && ((0, jsx_runtime_1.jsx)("div", { style: { position: 'absolute', top: '10px', left: '10px', zIndex: 10 }, children: (0, jsx_runtime_1.jsx)(EditableText_1.EditableBadge, { id: `${itemPath}.${badgeKey}`, "data-preview-field-path": `${itemPath}.${badgeKey}`, defaultValue: badge, badgeVariant: "primary" }) }))] }), (0, jsx_runtime_1.jsxs)("div", { style: {
326
+ }, "data-preview-static": "Open the stable live product detail page", style: { display: 'block', width: '100%', height: '100%', textDecoration: 'none' }, title: `View details for ${name}`, children: (0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${itemPath}.${imageKey}`, "data-preview-field-path": `${itemPath}.${imageKey}`, src: currentImage, fallbackSrc: imageFallback, alt: name, aspectRatio: isHorizontal ? 'square' : '4/3', fit: "cover", hoverZoom: true, style: { width: '100%', height: '100%', display: 'block' } }) })) : ((0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${itemPath}.${imageKey}`, "data-preview-field-path": `${itemPath}.${imageKey}`, src: currentImage, fallbackSrc: imageFallback, alt: name, aspectRatio: isHorizontal ? 'square' : '4/3', fit: "cover", hoverZoom: true, style: { width: '100%', height: '100%', display: 'block' } })), hasBadgeField ? ((0, jsx_runtime_1.jsx)("div", { style: {
327
+ position: 'absolute',
328
+ top: '10px',
329
+ left: '10px',
330
+ zIndex: 10,
331
+ ...(badge
332
+ ? {}
333
+ : {
334
+ width: 1,
335
+ height: 1,
336
+ overflow: 'hidden',
337
+ clip: 'rect(0, 0, 0, 0)',
338
+ }),
339
+ }, children: (0, jsx_runtime_1.jsx)(EditableText_1.EditableBadge, { id: `${itemPath}.${badgeKey}`, "data-preview-field-path": `${itemPath}.${badgeKey}`, defaultValue: badge, badgeVariant: "primary" }) })) : null] }), (0, jsx_runtime_1.jsxs)("div", { style: {
325
340
  padding: '1.25rem',
326
341
  display: 'flex',
327
342
  flexDirection: 'column',
@@ -363,10 +378,11 @@ function EditableProductCard({ itemPath, product: productProp, item: itemProp, c
363
378
  lineHeight: 1.35,
364
379
  color: 'var(--heading-color, #ffffff)',
365
380
  marginBottom: '0.375rem',
366
- } })), showDescription && description ? ((0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "p", id: `${itemPath}.${descriptionKey}`, "data-preview-field-path": `${itemPath}.${descriptionKey}`, defaultValue: description, style: {
381
+ } })), showDescription && (description || hasDescriptionField) ? ((0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { as: "p", id: `${itemPath}.${descriptionKey}`, "data-preview-field-path": `${itemPath}.${descriptionKey}`, defaultValue: description, style: {
367
382
  fontSize: '0.875rem',
368
383
  color: 'var(--muted-text, #94a3b8)',
369
384
  lineHeight: 1.5,
385
+ minHeight: description ? undefined : '1.25em',
370
386
  marginBottom: '0.625rem',
371
387
  display: '-webkit-box',
372
388
  WebkitLineClamp: 2,
@@ -0,0 +1,99 @@
1
+ /**
2
+ * EditableUniversalCard — DENEB UI Framework
3
+ *
4
+ * Universal, multi-industry listing card for all commerce and business templates:
5
+ * - 🚗 Automotive & Equipment: Make/model, year & condition, mileage, fuel, gearbox, price
6
+ * - 📚 Bookshops & Publishers: Cover photo, book title, author, published year, pages, price
7
+ * - 🏋️ Gyms & Fitness: Membership tier, duration, meal plan, 24/7 access, trainer, price
8
+ * - 🍽️ Restaurants & Menus: Dish photo, title, dietary tags, ingredients, prep time, price
9
+ * - 🏠 Real Estate & Rentals: Property image, location, beds, baths, sqft, price
10
+ * - 💼 Services & Agencies: Service title, duration, deliverable checklist, pricing
11
+ *
12
+ * Designed with strict editability architecture:
13
+ * 1. Automatically resolves fields from `useSiteData()` via `itemPath` or explicit field paths.
14
+ * 2. Properly manages z-index layers so title links never block clicks/hovers on inner text/specs.
15
+ * 3. Supports grid, list, compact, and horizontal orientations.
16
+ * 4. Extensible slots for images, badges, custom spec icons, and action buttons.
17
+ */
18
+ import React from 'react';
19
+ export interface UniversalCardSpec {
20
+ /** Machine identifier or field key (e.g. "mileage", "author", "duration", "mealPlan") */
21
+ key: string;
22
+ /** Optional human-readable label shown before or above the value (e.g. "Written by", "Validity") */
23
+ label?: string;
24
+ /** Spec display value (e.g. "52,000 km", "Harper Lee", "12 Months", "Vegetarian") */
25
+ value: string;
26
+ /** Optional decorative icon or badge rendered alongside the spec (ReactNode or icon name string) */
27
+ icon?: React.ReactNode | string;
28
+ /** Optional icon name string (e.g. "gauge", "fuel", "settings", "dumbbell", "book", "utensils") */
29
+ iconName?: string;
30
+ /** Explicit custom data-preview-field-path for the icon itself */
31
+ iconFieldPath?: string;
32
+ /** Explicit custom data-preview-field-path override */
33
+ fieldPath?: string;
34
+ }
35
+ export interface UniversalCardImageProps {
36
+ src?: string;
37
+ alt?: string;
38
+ aspectRatio?: '16/10' | '16/9' | '4/3' | '3/4' | '1/1' | string;
39
+ fit?: 'cover' | 'contain';
40
+ fallbackSrc?: string;
41
+ fieldPath?: string;
42
+ }
43
+ export interface UniversalCardCTA {
44
+ label: string;
45
+ href?: string;
46
+ onClick?: (e: React.MouseEvent) => void;
47
+ variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
48
+ fieldPath?: string;
49
+ }
50
+ export interface UniversalCardProps {
51
+ /**
52
+ * Root path in site-data.json for this card item.
53
+ * e.g. "cards.vehicles.am-1001", "cards.books.book-42", "cards.plans.gold"
54
+ * All sub-fields inherit this prefix unless individually overridden.
55
+ */
56
+ itemPath?: string;
57
+ /** Primary image configuration or image URL string */
58
+ image?: UniversalCardImageProps | string;
59
+ /** Slot to render a custom image component (e.g. Next Image, VehicleImage, CoverImage) */
60
+ imageSlot?: React.ReactNode;
61
+ /** Top floating badge label (e.g. "Available", "Bestseller", "New", "Chef's Pick") */
62
+ badge?: string;
63
+ badgeVariant?: 'default' | 'success' | 'warning' | 'primary' | 'muted';
64
+ badgeSlot?: React.ReactNode;
65
+ /** Eyebrow / Overline text (e.g. "2018 · Registered", "Science Fiction", "Tier 1") */
66
+ overline?: string;
67
+ /** Main entity title / heading (e.g. "Mercedes-Benz C200", "Dune", "Gold Membership") */
68
+ title: string;
69
+ /** Link URL for the title and card navigation */
70
+ href?: string;
71
+ /** Subtitle / Edition / Summary / Author text */
72
+ subtitle?: string;
73
+ /** Optional longer description or excerpt */
74
+ description?: string;
75
+ /** Dynamic key-value specifications, attributes, or feature pills */
76
+ specs?: UniversalCardSpec[];
77
+ /** Primary price display string (e.g. "LKR 27,500,000", "$19.99", "$49") */
78
+ price?: string;
79
+ /** Compare-at or original strike-through price */
80
+ originalPrice?: string;
81
+ /** Price label shown above the price (e.g. "Price", "Starting from", "Membership") */
82
+ priceLabel?: string;
83
+ /** Suffix appended after the price (e.g. "/mo", "/year", "/person") */
84
+ priceSuffix?: string;
85
+ /** Call-to-action button configuration */
86
+ cta?: UniversalCardCTA;
87
+ /** Custom slot for secondary action buttons (e.g. Save, Compare, Bookmark, Share) */
88
+ actionsSlot?: React.ReactNode;
89
+ /** Layout orientation */
90
+ layout?: 'grid' | 'list' | 'compact' | 'horizontal';
91
+ /** Outer CSS class */
92
+ className?: string;
93
+ /** Outer inline style */
94
+ style?: React.CSSProperties;
95
+ /** Extra children rendered at the bottom of the card */
96
+ children?: React.ReactNode;
97
+ }
98
+ export declare function EditableUniversalCard({ itemPath, image, imageSlot, badge, badgeVariant, badgeSlot, overline, title, href, subtitle, description, specs, price, originalPrice, priceLabel, priceSuffix, cta, actionsSlot, layout, className, style, children, }: UniversalCardProps): React.ReactElement;
99
+ export { EditableUniversalCard as UniversalCard, EditableUniversalCard as EditableListingCard, EditableUniversalCard as ListingCard, EditableUniversalCard as EditableEntityCard, EditableUniversalCard as EntityCard, };
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.EditableUniversalCard = EditableUniversalCard;
5
+ exports.UniversalCard = EditableUniversalCard;
6
+ exports.EditableListingCard = EditableUniversalCard;
7
+ exports.ListingCard = EditableUniversalCard;
8
+ exports.EditableEntityCard = EditableUniversalCard;
9
+ exports.EntityCard = EditableUniversalCard;
10
+ const jsx_runtime_1 = require("react/jsx-runtime");
11
+ const SiteDataProvider_1 = require("./SiteDataProvider");
12
+ const EditableImage_1 = require("./EditableImage");
13
+ const EditableIcon_1 = require("./EditableIcon");
14
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
15
+ function resolveFieldFromPath(content, fullPath) {
16
+ if (!content || typeof content !== 'object' || !fullPath)
17
+ return undefined;
18
+ const parts = fullPath.split('.');
19
+ let curr = content;
20
+ for (const part of parts) {
21
+ if (curr && typeof curr === 'object' && part in curr) {
22
+ curr = curr[part];
23
+ }
24
+ else {
25
+ return undefined;
26
+ }
27
+ }
28
+ return typeof curr === 'string' && curr ? curr : undefined;
29
+ }
30
+ // ─── Component Implementation ─────────────────────────────────────────────────
31
+ function EditableUniversalCard({ itemPath, image, imageSlot, badge, badgeVariant = 'primary', badgeSlot, overline, title, href, subtitle, description, specs = [], price, originalPrice, priceLabel, priceSuffix, cta, actionsSlot, layout = 'grid', className = '', style, children, }) {
32
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
33
+ const content = siteData?.content;
34
+ // Resolve item-level field paths
35
+ const fp = (subKey) => (itemPath ? `${itemPath}.${subKey}` : undefined);
36
+ // Resolved dynamic values with live siteData overrides
37
+ const resolvedOverline = (itemPath ? resolveFieldFromPath(content, fp('overline') || '') : undefined) ||
38
+ (itemPath ? resolveFieldFromPath(content, fp('meta') || '') : undefined) ||
39
+ overline;
40
+ const resolvedTitle = (itemPath ? resolveFieldFromPath(content, fp('title') || '') : undefined) ||
41
+ (itemPath ? resolveFieldFromPath(content, fp('name') || '') : undefined) ||
42
+ title;
43
+ const resolvedSubtitle = (itemPath ? resolveFieldFromPath(content, fp('subtitle') || '') : undefined) ||
44
+ (itemPath ? resolveFieldFromPath(content, fp('details') || '') : undefined) ||
45
+ (itemPath ? resolveFieldFromPath(content, fp('author') || '') : undefined) ||
46
+ subtitle;
47
+ const resolvedDescription = (itemPath ? resolveFieldFromPath(content, fp('description') || '') : undefined) ||
48
+ (itemPath ? resolveFieldFromPath(content, fp('summary') || '') : undefined) ||
49
+ description;
50
+ const resolvedBadge = (itemPath ? resolveFieldFromPath(content, fp('badge') || '') : undefined) ||
51
+ (itemPath ? resolveFieldFromPath(content, fp('status') || '') : undefined) ||
52
+ badge;
53
+ const resolvedPrice = (itemPath ? resolveFieldFromPath(content, fp('price') || '') : undefined) ||
54
+ price;
55
+ const resolvedOriginalPrice = (itemPath ? resolveFieldFromPath(content, fp('originalPrice') || '') : undefined) ||
56
+ originalPrice;
57
+ const resolvedPriceLabel = (itemPath ? resolveFieldFromPath(content, fp('priceLabel') || '') : undefined) ||
58
+ priceLabel;
59
+ // Image source normalization
60
+ const imageObj = typeof image === 'string'
61
+ ? { src: image }
62
+ : image || {};
63
+ const isList = layout === 'list' || layout === 'horizontal';
64
+ // Badge variant styling
65
+ const badgeStyles = {
66
+ default: { backgroundColor: 'var(--tag-bg, rgba(255,255,255,0.9))', color: 'var(--text-color, #0f172a)' },
67
+ primary: { backgroundColor: 'var(--brand-accent, #0284c7)', color: '#ffffff' },
68
+ success: { backgroundColor: '#10b981', color: '#ffffff' },
69
+ warning: { backgroundColor: '#f59e0b', color: '#0f172a' },
70
+ muted: { backgroundColor: 'rgba(100, 116, 139, 0.85)', color: '#ffffff' },
71
+ };
72
+ return ((0, jsx_runtime_1.jsxs)("article", { "data-preview-item-path": itemPath, className: `deneb-universal-card ${className}`, style: {
73
+ position: 'relative',
74
+ display: 'flex',
75
+ flexDirection: isList ? 'row' : 'column',
76
+ borderRadius: 'var(--card-radius, 0.75rem)',
77
+ border: '1px solid var(--card-border, rgba(226, 232, 240, 0.8))',
78
+ backgroundColor: 'var(--card-bg, #ffffff)',
79
+ overflow: 'hidden',
80
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease',
81
+ ...style,
82
+ }, children: [(imageSlot || imageObj.src) && ((0, jsx_runtime_1.jsxs)("div", { style: {
83
+ position: 'relative',
84
+ zIndex: 10,
85
+ overflow: 'hidden',
86
+ width: isList ? '40%' : '100%',
87
+ flexShrink: 0,
88
+ }, children: [imageSlot ? (imageSlot) : imageObj.src ? ((0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: imageObj.fieldPath || fp('image') || fp('imageUrl') || 'image', "data-preview-field-path": imageObj.fieldPath || fp('image') || fp('imageUrl'), src: imageObj.src, alt: imageObj.alt || resolvedTitle, fallbackSrc: imageObj.fallbackSrc, aspectRatio: imageObj.aspectRatio || (isList ? '1/1' : '16/10'), fit: imageObj.fit || 'cover', hoverZoom: true, style: { width: '100%', height: '100%', display: 'block' } })) : null, (badgeSlot || resolvedBadge) && ((0, jsx_runtime_1.jsx)("div", { style: {
89
+ position: 'absolute',
90
+ top: '0.75rem',
91
+ left: '0.75rem',
92
+ zIndex: 20,
93
+ pointerEvents: 'auto',
94
+ }, children: badgeSlot ? (badgeSlot) : ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fp('badge'), "data-preview-style-target": fp('badge'), "data-preview-style-type": "text", style: {
95
+ display: 'inline-flex',
96
+ alignItems: 'center',
97
+ padding: '0.25rem 0.625rem',
98
+ fontSize: '0.75rem',
99
+ fontWeight: 700,
100
+ borderRadius: '9999px',
101
+ boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
102
+ backdropFilter: 'blur(4px)',
103
+ ...badgeStyles[badgeVariant],
104
+ }, children: resolvedBadge })) }))] })), (0, jsx_runtime_1.jsxs)("div", { style: {
105
+ display: 'flex',
106
+ flexDirection: 'column',
107
+ flex: 1,
108
+ justifyContent: 'space-between',
109
+ padding: '1.25rem',
110
+ gap: '0.875rem',
111
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', flexDirection: 'column', gap: '0.35rem' }, children: [resolvedOverline && ((0, jsx_runtime_1.jsx)("p", { style: {
112
+ position: 'relative',
113
+ zIndex: 10,
114
+ fontSize: '0.75rem',
115
+ fontWeight: 600,
116
+ letterSpacing: '0.05em',
117
+ textTransform: 'uppercase',
118
+ color: 'var(--muted-text, #64748b)',
119
+ margin: 0,
120
+ }, "data-preview-field-path": fp('overline') || fp('meta'), "data-preview-style-target": fp('overline') || fp('meta'), "data-preview-style-type": "text", children: resolvedOverline })), (0, jsx_runtime_1.jsx)("h3", { style: {
121
+ fontSize: '1.25rem',
122
+ fontWeight: 700,
123
+ lineHeight: 1.3,
124
+ color: 'var(--heading-color, #0f172a)',
125
+ margin: 0,
126
+ }, children: href ? ((0, jsx_runtime_1.jsx)("a", { href: href, "data-preview-field-path": fp('title') || fp('name'), "data-preview-style-target": fp('title') || fp('name'), "data-preview-style-type": "text", style: {
127
+ color: 'inherit',
128
+ textDecoration: 'none',
129
+ outline: 'none',
130
+ }, children: resolvedTitle })) : ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fp('title') || fp('name'), "data-preview-style-target": fp('title') || fp('name'), "data-preview-style-type": "text", children: resolvedTitle })) }), resolvedSubtitle && ((0, jsx_runtime_1.jsx)("p", { style: {
131
+ position: 'relative',
132
+ zIndex: 10,
133
+ fontSize: '0.875rem',
134
+ color: 'var(--muted-text, #64748b)',
135
+ margin: 0,
136
+ }, "data-preview-field-path": fp('subtitle') || fp('details'), "data-preview-style-target": fp('subtitle') || fp('details'), "data-preview-style-type": "text", children: resolvedSubtitle })), resolvedDescription && ((0, jsx_runtime_1.jsx)("p", { style: {
137
+ position: 'relative',
138
+ zIndex: 10,
139
+ fontSize: '0.825rem',
140
+ lineHeight: 1.5,
141
+ color: 'var(--body-text, #334155)',
142
+ margin: '0.25rem 0 0 0',
143
+ display: '-webkit-box',
144
+ WebkitLineClamp: 2,
145
+ WebkitBoxOrient: 'vertical',
146
+ overflow: 'hidden',
147
+ }, "data-preview-field-path": fp('description') || fp('summary'), "data-preview-style-target": fp('description') || fp('summary'), "data-preview-style-type": "text", children: resolvedDescription }))] }), specs.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { "aria-label": "Attributes", style: {
148
+ position: 'relative',
149
+ zIndex: 10,
150
+ display: 'flex',
151
+ flexWrap: 'wrap',
152
+ gap: '0.5rem 1rem',
153
+ listStyle: 'none',
154
+ padding: 0,
155
+ margin: '0.25rem 0',
156
+ }, children: specs.map((spec) => {
157
+ const specField = spec.fieldPath || (itemPath ? `${itemPath}.specs.${spec.key}` : spec.key);
158
+ const dynamicVal = itemPath
159
+ ? resolveFieldFromPath(content, specField) ||
160
+ resolveFieldFromPath(content, `${itemPath}.${spec.key}`) ||
161
+ spec.value
162
+ : spec.value;
163
+ return ((0, jsx_runtime_1.jsxs)("li", { style: {
164
+ display: 'inline-flex',
165
+ alignItems: 'center',
166
+ gap: '0.375rem',
167
+ fontSize: '0.825rem',
168
+ color: 'var(--muted-text, #64748b)',
169
+ }, "data-preview-field-path": specField, "data-preview-style-target": specField, "data-preview-style-type": "text", children: [spec.icon && ((0, jsx_runtime_1.jsx)("span", { style: { display: 'inline-flex', alignItems: 'center' }, children: typeof spec.icon === 'string' ? ((0, jsx_runtime_1.jsx)(EditableIcon_1.EditableIcon, { name: spec.icon, fieldPath: spec.iconFieldPath || (itemPath ? `${itemPath}.specs.${spec.key}.icon` : undefined), size: 14 })) : (spec.icon) })), !spec.icon && spec.iconName && ((0, jsx_runtime_1.jsx)("span", { style: { display: 'inline-flex', alignItems: 'center' }, children: (0, jsx_runtime_1.jsx)(EditableIcon_1.EditableIcon, { name: spec.iconName, fieldPath: spec.iconFieldPath || (itemPath ? `${itemPath}.specs.${spec.key}.icon` : undefined), size: 14 }) })), spec.label && (0, jsx_runtime_1.jsxs)("span", { style: { fontWeight: 500 }, children: [spec.label, ":"] }), (0, jsx_runtime_1.jsx)("span", { style: { fontWeight: 600, color: 'var(--body-text, #1e293b)' }, children: dynamicVal })] }, spec.key));
170
+ }) })), (resolvedPrice || cta || actionsSlot) && ((0, jsx_runtime_1.jsxs)("div", { style: {
171
+ position: 'relative',
172
+ zIndex: 10,
173
+ marginTop: 'auto',
174
+ paddingTop: '0.75rem',
175
+ borderTop: '1px solid var(--card-border, rgba(226, 232, 240, 0.7))',
176
+ display: 'flex',
177
+ alignItems: 'center',
178
+ justifyContent: 'space-between',
179
+ gap: '0.75rem',
180
+ flexWrap: 'wrap',
181
+ }, children: [resolvedPrice && ((0, jsx_runtime_1.jsxs)("div", { children: [resolvedPriceLabel && ((0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": fp('priceLabel'), "data-preview-style-target": fp('priceLabel'), "data-preview-style-type": "text", style: {
182
+ fontSize: '0.7rem',
183
+ color: 'var(--muted-text, #64748b)',
184
+ margin: 0,
185
+ }, children: resolvedPriceLabel })), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'baseline', gap: '0.35rem' }, children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fp('price'), "data-preview-style-target": fp('price'), "data-preview-style-type": "text", style: {
186
+ fontSize: '1.25rem',
187
+ fontWeight: 800,
188
+ color: 'var(--price-color, var(--heading-color, #0f172a))',
189
+ fontVariantNumeric: 'tabular-nums',
190
+ }, children: resolvedPrice }), resolvedOriginalPrice && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fp('originalPrice'), "data-preview-style-target": fp('originalPrice'), "data-preview-style-type": "text", style: {
191
+ fontSize: '0.825rem',
192
+ textDecoration: 'line-through',
193
+ color: 'var(--muted-text, #94a3b8)',
194
+ }, children: resolvedOriginalPrice })), priceSuffix && ((0, jsx_runtime_1.jsx)("span", { style: { fontSize: '0.75rem', color: 'var(--muted-text, #64748b)' }, children: priceSuffix }))] })] })), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem', marginLeft: 'auto' }, children: [actionsSlot, cta && (cta.href ? ((0, jsx_runtime_1.jsx)("a", { href: cta.href, "data-preview-field-path": cta.fieldPath || fp('ctaText') || fp('ctaLabel'), "data-preview-style-target": cta.fieldPath || fp('ctaText') || fp('ctaLabel'), "data-preview-style-type": "text", style: {
195
+ display: 'inline-flex',
196
+ alignItems: 'center',
197
+ justifyContent: 'center',
198
+ padding: '0.45rem 0.875rem',
199
+ fontSize: '0.825rem',
200
+ fontWeight: 600,
201
+ borderRadius: 'var(--button-radius, 0.5rem)',
202
+ backgroundColor: 'var(--brand-accent, #0284c7)',
203
+ color: '#ffffff',
204
+ textDecoration: 'none',
205
+ transition: 'opacity 0.15s ease',
206
+ }, children: cta.label })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: cta.onClick, "data-preview-field-path": cta.fieldPath || fp('ctaText') || fp('ctaLabel'), "data-preview-style-target": cta.fieldPath || fp('ctaText') || fp('ctaLabel'), "data-preview-style-type": "text", style: {
207
+ display: 'inline-flex',
208
+ alignItems: 'center',
209
+ justifyContent: 'center',
210
+ padding: '0.45rem 0.875rem',
211
+ fontSize: '0.825rem',
212
+ fontWeight: 600,
213
+ borderRadius: 'var(--button-radius, 0.5rem)',
214
+ backgroundColor: 'var(--brand-accent, #0284c7)',
215
+ color: '#ffffff',
216
+ border: 'none',
217
+ cursor: 'pointer',
218
+ transition: 'opacity 0.15s ease',
219
+ }, children: cta.label })))] })] })), children] })] }));
220
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * EditableVehicleCard — Deneb UI
3
+ *
4
+ * A generic, fully Deneb-editable vehicle / product listing card for
5
+ * automotive dealership templates.
6
+ *
7
+ * Field path convention:
8
+ * cards.vehicles.{id}.name — Car name
9
+ * cards.vehicles.{id}.meta — "Year · Condition"
10
+ * cards.vehicles.{id}.details — Variant / trim level
11
+ * cards.vehicles.{id}.badge — Status label (Available / Reserved / Sold)
12
+ * cards.vehicles.{id}.mileage — Mileage string (e.g. "68,500 km")
13
+ * cards.vehicles.{id}.fuel — Fuel type string
14
+ * cards.vehicles.{id}.transmission — Transmission string
15
+ * cards.vehicles.{id}.price — Display price string (e.g. "LKR 9,450,000")
16
+ * photos.vehicles.{id}.image0 — Primary photo (replaced by editor)
17
+ *
18
+ * Usage:
19
+ * <EditableVehicleCard vehicleId="am-1001" href="/cars/2015-toyota-aqua-..." />
20
+ *
21
+ * The component reads field values from the SiteDataProvider context.
22
+ * It renders nothing if used outside a SiteDataProvider (graceful no-op).
23
+ */
24
+ import React from 'react';
25
+ export interface VehicleCardFields {
26
+ name?: string;
27
+ meta?: string;
28
+ details?: string;
29
+ badge?: string;
30
+ mileage?: string;
31
+ fuel?: string;
32
+ transmission?: string;
33
+ price?: string;
34
+ }
35
+ export interface EditableVehicleCardProps {
36
+ /** Vehicle ID — must match a key in cards.vehicles and photos.vehicles */
37
+ vehicleId: string;
38
+ /** Link href for the card */
39
+ href?: string;
40
+ /** CSS class for the outer article element */
41
+ className?: string;
42
+ /** Slot for the image element (usually <VehicleImage />) */
43
+ imageSlot?: React.ReactNode;
44
+ /** Slot for action buttons (CTA, save, compare) */
45
+ actionsSlot?: React.ReactNode;
46
+ /** Layout orientation */
47
+ layout?: 'grid' | 'list';
48
+ /** Children rendered inside the card body (fallback / extra content) */
49
+ children?: React.ReactNode;
50
+ }
51
+ declare function readField(content: unknown, vehicleId: string, field: string, fallback?: string): string;
52
+ export declare function vehicleFieldPath(vehicleId: string, field: string): string;
53
+ /**
54
+ * EditableVehicleCard renders a skeleton vehicle card driven by site-data.json.
55
+ * Templates should wrap or extend this component with their own visual design.
56
+ *
57
+ * Each text span has data-preview-field-path set so the Deneb Lab can edit
58
+ * it directly.
59
+ */
60
+ export declare function EditableVehicleCard({ vehicleId, href, className, imageSlot, actionsSlot, layout, children, }: EditableVehicleCardProps): React.ReactElement | null;
61
+ export interface EditableBrandCardProps {
62
+ /** Make slug — must match a key in cards.brands (e.g. "toyota", "bmw") */
63
+ makeSlug: string;
64
+ /** Link href */
65
+ href?: string;
66
+ className?: string;
67
+ /** Fallback display name when no site-data value is set */
68
+ fallbackName?: string;
69
+ /** Fallback count label */
70
+ fallbackCountLabel?: string;
71
+ children?: React.ReactNode;
72
+ }
73
+ declare function readBrandField(content: unknown, makeSlug: string, field: string, fallback?: string): string;
74
+ /**
75
+ * EditableBrandCard — renders a brand filter card with editable name and count.
76
+ */
77
+ export declare function EditableBrandCard({ makeSlug, href, className, fallbackName, fallbackCountLabel, children, }: EditableBrandCardProps): React.ReactElement | null;
78
+ export interface EditableBodyTypeCardProps {
79
+ /** Body type slug — must match a key in cards.bodyTypes (e.g. "hatchback", "suv") */
80
+ typeSlug: string;
81
+ href?: string;
82
+ className?: string;
83
+ fallbackName?: string;
84
+ fallbackDescription?: string;
85
+ imageSlot?: React.ReactNode;
86
+ children?: React.ReactNode;
87
+ }
88
+ declare function readBodyTypeField(content: unknown, typeSlug: string, field: string, fallback?: string): string;
89
+ /**
90
+ * EditableBodyTypeCard — renders a body-type filter card with editable name and description.
91
+ */
92
+ export declare function EditableBodyTypeCard({ typeSlug, href, className, fallbackName, fallbackDescription, imageSlot, children, }: EditableBodyTypeCardProps): React.ReactElement | null;
93
+ export interface EditableShowroomCardProps {
94
+ showroomId: string;
95
+ fallbackName?: string;
96
+ fallbackAddress?: string;
97
+ fallbackNotes?: string;
98
+ className?: string;
99
+ children?: React.ReactNode;
100
+ }
101
+ declare function readShowroomField(content: unknown, id: string, field: string, fallback?: string): string;
102
+ /**
103
+ * EditableShowroomCard — renders showroom name, address, and notes
104
+ * with data-preview-field-path attributes for Deneb Lab editing.
105
+ */
106
+ export declare function EditableShowroomCard({ showroomId, fallbackName, fallbackAddress, fallbackNotes, className, children, }: EditableShowroomCardProps): React.ReactElement | null;
107
+ export { readField as readVehicleCardField, readBrandField, readBodyTypeField, readShowroomField, };
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.vehicleFieldPath = vehicleFieldPath;
5
+ exports.EditableVehicleCard = EditableVehicleCard;
6
+ exports.EditableBrandCard = EditableBrandCard;
7
+ exports.EditableBodyTypeCard = EditableBodyTypeCard;
8
+ exports.EditableShowroomCard = EditableShowroomCard;
9
+ exports.readVehicleCardField = readField;
10
+ exports.readBrandField = readBrandField;
11
+ exports.readBodyTypeField = readBodyTypeField;
12
+ exports.readShowroomField = readShowroomField;
13
+ const jsx_runtime_1 = require("react/jsx-runtime");
14
+ const SiteDataProvider_1 = require("./SiteDataProvider");
15
+ const EditableIcon_1 = require("./EditableIcon");
16
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
17
+ function readField(content, vehicleId, field, fallback = '') {
18
+ const cards = content
19
+ ?.cards;
20
+ const vehicles = cards?.vehicles;
21
+ const entry = vehicles?.[vehicleId];
22
+ const val = entry?.[field];
23
+ return typeof val === 'string' && val ? val : fallback;
24
+ }
25
+ function vehicleFieldPath(vehicleId, field) {
26
+ return `cards.vehicles.${vehicleId}.${field}`;
27
+ }
28
+ // ─── Component ────────────────────────────────────────────────────────────────
29
+ /**
30
+ * EditableVehicleCard renders a skeleton vehicle card driven by site-data.json.
31
+ * Templates should wrap or extend this component with their own visual design.
32
+ *
33
+ * Each text span has data-preview-field-path set so the Deneb Lab can edit
34
+ * it directly.
35
+ */
36
+ function EditableVehicleCard({ vehicleId, href, className, imageSlot, actionsSlot, layout = 'grid', children, }) {
37
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
38
+ if (!siteData)
39
+ return null;
40
+ const c = siteData.content;
41
+ const name = readField(c, vehicleId, 'name');
42
+ const meta = readField(c, vehicleId, 'meta');
43
+ const details = readField(c, vehicleId, 'details');
44
+ const badge = readField(c, vehicleId, 'badge');
45
+ const mileage = readField(c, vehicleId, 'mileage');
46
+ const fuel = readField(c, vehicleId, 'fuel');
47
+ const transmission = readField(c, vehicleId, 'transmission');
48
+ const price = readField(c, vehicleId, 'price');
49
+ const fp = (field) => vehicleFieldPath(vehicleId, field);
50
+ return ((0, jsx_runtime_1.jsxs)("article", { className: className, "data-preview-item-path": `cards.vehicles.${vehicleId}`, children: [imageSlot, badge && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fp('badge'), "data-preview-style-target": fp('badge'), "data-preview-style-type": "text", children: badge })), (0, jsx_runtime_1.jsxs)("div", { children: [meta && ((0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": fp('meta'), "data-preview-style-target": fp('meta'), "data-preview-style-type": "text", children: meta })), href ? ((0, jsx_runtime_1.jsx)("a", { href: href, "data-preview-field-path": fp('name'), "data-preview-style-target": fp('name'), "data-preview-style-type": "text", children: name })) : ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": fp('name'), "data-preview-style-target": fp('name'), "data-preview-style-type": "text", children: name })), details && ((0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": fp('details'), "data-preview-style-target": fp('details'), "data-preview-style-type": "text", children: details })), (0, jsx_runtime_1.jsxs)("ul", { children: [mileage && ((0, jsx_runtime_1.jsxs)("li", { "data-preview-field-path": fp('mileage'), "data-preview-style-target": fp('mileage'), "data-preview-style-type": "text", children: [(0, jsx_runtime_1.jsx)(EditableIcon_1.EditableIcon, { name: "gauge", fieldPath: fp('specs.mileage.icon'), size: 14, style: { marginRight: '0.25rem' } }), mileage] })), fuel && ((0, jsx_runtime_1.jsxs)("li", { "data-preview-field-path": fp('fuel'), "data-preview-style-target": fp('fuel'), "data-preview-style-type": "text", children: [(0, jsx_runtime_1.jsx)(EditableIcon_1.EditableIcon, { name: "fuel", fieldPath: fp('specs.fuel.icon'), size: 14, style: { marginRight: '0.25rem' } }), fuel] })), transmission && ((0, jsx_runtime_1.jsxs)("li", { "data-preview-field-path": fp('transmission'), "data-preview-style-target": fp('transmission'), "data-preview-style-type": "text", children: [(0, jsx_runtime_1.jsx)(EditableIcon_1.EditableIcon, { name: "settings", fieldPath: fp('specs.transmission.icon'), size: 14, style: { marginRight: '0.25rem' } }), transmission] }))] }), price && ((0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": fp('price'), "data-preview-style-target": fp('price'), "data-preview-style-type": "text", children: price }))] }), actionsSlot, children] }));
51
+ }
52
+ function readBrandField(content, makeSlug, field, fallback = '') {
53
+ const cards = content
54
+ ?.cards;
55
+ const brands = cards?.brands;
56
+ const entry = brands?.[makeSlug];
57
+ const val = entry?.[field];
58
+ return typeof val === 'string' && val ? val : fallback;
59
+ }
60
+ /**
61
+ * EditableBrandCard — renders a brand filter card with editable name and count.
62
+ */
63
+ function EditableBrandCard({ makeSlug, href, className, fallbackName = '', fallbackCountLabel = '', children, }) {
64
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
65
+ const c = siteData?.content;
66
+ const displayName = readBrandField(c, makeSlug, 'displayName', fallbackName);
67
+ const countLabel = readBrandField(c, makeSlug, 'countLabel', fallbackCountLabel);
68
+ const bfp = (field) => `cards.brands.${makeSlug}.${field}`;
69
+ const inner = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": bfp('displayName'), "data-preview-style-target": bfp('displayName'), "data-preview-style-type": "text", children: displayName }), (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": bfp('countLabel'), "data-preview-style-target": bfp('countLabel'), "data-preview-style-type": "text", children: countLabel }), children] }));
70
+ if (href) {
71
+ return (0, jsx_runtime_1.jsx)("a", { href: href, className: className, "data-preview-item-path": `cards.brands.${makeSlug}`, children: inner });
72
+ }
73
+ return (0, jsx_runtime_1.jsx)("div", { className: className, "data-preview-item-path": `cards.brands.${makeSlug}`, children: inner });
74
+ }
75
+ function readBodyTypeField(content, typeSlug, field, fallback = '') {
76
+ const cards = content
77
+ ?.cards;
78
+ const bodyTypes = cards?.bodyTypes;
79
+ const entry = bodyTypes?.[typeSlug];
80
+ const val = entry?.[field];
81
+ return typeof val === 'string' && val ? val : fallback;
82
+ }
83
+ /**
84
+ * EditableBodyTypeCard — renders a body-type filter card with editable name and description.
85
+ */
86
+ function EditableBodyTypeCard({ typeSlug, href, className, fallbackName = '', fallbackDescription = '', imageSlot, children, }) {
87
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
88
+ const c = siteData?.content;
89
+ const displayName = readBodyTypeField(c, typeSlug, 'displayName', fallbackName);
90
+ const description = readBodyTypeField(c, typeSlug, 'description', fallbackDescription);
91
+ const tfp = (field) => `cards.bodyTypes.${typeSlug}.${field}`;
92
+ const inner = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [imageSlot, (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": tfp('displayName'), "data-preview-style-target": tfp('displayName'), "data-preview-style-type": "text", children: displayName }), description && ((0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": tfp('description'), "data-preview-style-target": tfp('description'), "data-preview-style-type": "text", children: description })), children] }));
93
+ if (href) {
94
+ return (0, jsx_runtime_1.jsx)("a", { href: href, className: className, "data-preview-item-path": `cards.bodyTypes.${typeSlug}`, children: inner });
95
+ }
96
+ return (0, jsx_runtime_1.jsx)("div", { className: className, "data-preview-item-path": `cards.bodyTypes.${typeSlug}`, children: inner });
97
+ }
98
+ function readShowroomField(content, id, field, fallback = '') {
99
+ const showrooms = content
100
+ ?.showrooms;
101
+ const entry = showrooms?.[id];
102
+ const val = entry?.[field];
103
+ return typeof val === 'string' && val ? val : fallback;
104
+ }
105
+ /**
106
+ * EditableShowroomCard — renders showroom name, address, and notes
107
+ * with data-preview-field-path attributes for Deneb Lab editing.
108
+ */
109
+ function EditableShowroomCard({ showroomId, fallbackName = '', fallbackAddress = '', fallbackNotes = '', className, children, }) {
110
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
111
+ const c = siteData?.content;
112
+ const name = readShowroomField(c, showroomId, 'name', fallbackName);
113
+ const address = readShowroomField(c, showroomId, 'address', fallbackAddress);
114
+ const notes = readShowroomField(c, showroomId, 'notes', fallbackNotes);
115
+ const sfp = (field) => `showrooms.${showroomId}.${field}`;
116
+ return ((0, jsx_runtime_1.jsxs)("div", { className: className, "data-preview-item-path": `showrooms.${showroomId}`, children: [(0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": sfp('name'), "data-preview-style-target": sfp('name'), "data-preview-style-type": "text", children: name }), (0, jsx_runtime_1.jsx)("address", { "data-preview-field-path": sfp('address'), "data-preview-style-target": sfp('address'), "data-preview-style-type": "text", children: address }), notes && ((0, jsx_runtime_1.jsx)("p", { "data-preview-field-path": sfp('notes'), "data-preview-style-target": sfp('notes'), "data-preview-style-type": "text", children: notes })), children] }));
117
+ }
@@ -102,16 +102,7 @@ export declare const DenebDataProvider: typeof SiteDataProvider;
102
102
  export declare const useDenebData: typeof useSiteData;
103
103
  export declare const DenebDataContext: React.Context<SiteData | typeof UNINITIALIZED_SITE_DATA>;
104
104
  export type DenebData = SiteData;
105
- /**
106
- * Hook to retrieve products cleanly from SiteData, supporting both
107
- * top-level content.products and nested content.home.products.
108
- */
109
- /**
110
- * Universal Bidirectional Product Normalizer:
111
- * Ensures every product object has both canonical and alias keys simultaneously populated
112
- * with clean numbers and resolved image URLs, so any template component code works flawlessly
113
- * regardless of whether the developer wrote .title or .name, .cost or .price, .image or .imageUrl.
114
- */
105
+ export declare function formatWeeklyHoursToString(value: unknown): string;
115
106
  export declare function normalizeProductItem(p: unknown): ProductItem;
116
107
  /**
117
108
  * Universal Service Normalizer
@@ -18,6 +18,7 @@ exports.contentNumber = contentNumber;
18
18
  exports.parseFieldPath = parseFieldPath;
19
19
  exports.getFieldStyle = getFieldStyle;
20
20
  exports.useFieldStyle = useFieldStyle;
21
+ exports.formatWeeklyHoursToString = formatWeeklyHoursToString;
21
22
  exports.normalizeProductItem = normalizeProductItem;
22
23
  exports.normalizeServiceItem = normalizeServiceItem;
23
24
  exports.useProducts = useProducts;
@@ -356,9 +357,11 @@ function SiteDataProvider({ children, initialSiteData, fallbackSiteData, liveCat
356
357
  }
357
358
  }
358
359
  if (live.shop.openingHours) {
360
+ const hoursStr = formatWeeklyHoursToString(live.shop.openingHours);
361
+ const liveText = hoursStr || (typeof live.shop.openingHours === "string" ? live.shop.openingHours : "");
359
362
  nextContact.hours = live.shop.openingHours;
360
- nextContact.openingHours = live.shop.openingHours;
361
- nextCommon.openingHours = live.shop.openingHours;
363
+ nextContact.openingHours = liveText;
364
+ nextCommon.openingHours = liveText;
362
365
  nextCommon.hours = live.shop.openingHours;
363
366
  nextHome.businessHours = live.shop.openingHours;
364
367
  }
@@ -718,6 +721,78 @@ exports.DenebDataContext = exports.SiteDataContext;
718
721
  * with clean numbers and resolved image URLs, so any template component code works flawlessly
719
722
  * regardless of whether the developer wrote .title or .name, .cost or .price, .image or .imageUrl.
720
723
  */
724
+ function formatTime12(timeStr) {
725
+ if (!timeStr)
726
+ return "";
727
+ const clean = timeStr.trim();
728
+ if (/am|pm/i.test(clean))
729
+ return clean;
730
+ const [hourStr, minStr = "00"] = clean.split(":");
731
+ const hour = parseInt(hourStr, 10);
732
+ if (isNaN(hour))
733
+ return clean;
734
+ const ampm = hour >= 12 ? "PM" : "AM";
735
+ const formattedHour = hour % 12 === 0 ? 12 : hour % 12;
736
+ return `${formattedHour}:${minStr.padStart(2, "0")} ${ampm}`;
737
+ }
738
+ function formatWeeklyHoursToString(value) {
739
+ if (typeof value === "string")
740
+ return value.trim();
741
+ if (!value || typeof value !== "object" || Array.isArray(value))
742
+ return "";
743
+ const days = [
744
+ ["monday", "Mon"],
745
+ ["tuesday", "Tue"],
746
+ ["wednesday", "Wed"],
747
+ ["thursday", "Thu"],
748
+ ["friday", "Fri"],
749
+ ["saturday", "Sat"],
750
+ ["sunday", "Sun"],
751
+ ];
752
+ const entries = [];
753
+ const rec = value;
754
+ for (const [key, label] of days) {
755
+ const day = rec[key];
756
+ if (!day)
757
+ continue;
758
+ if (typeof day === "string") {
759
+ const trimmed = day.trim();
760
+ if (trimmed)
761
+ entries.push({ label, hours: trimmed });
762
+ continue;
763
+ }
764
+ if (day.closed === true) {
765
+ entries.push({ label, hours: "Closed" });
766
+ continue;
767
+ }
768
+ const open = typeof day.open === "string" ? formatTime12(day.open) : "";
769
+ const close = typeof day.close === "string" ? formatTime12(day.close) : "";
770
+ if (open && close) {
771
+ entries.push({ label, hours: `${open} – ${close}` });
772
+ }
773
+ else if (open) {
774
+ entries.push({ label, hours: `From ${open}` });
775
+ }
776
+ }
777
+ if (entries.length === 0)
778
+ return "";
779
+ const groups = [];
780
+ for (const entry of entries) {
781
+ const prev = groups[groups.length - 1];
782
+ if (prev && prev.hours === entry.hours) {
783
+ prev.last = entry.label;
784
+ }
785
+ else {
786
+ groups.push({ first: entry.label, last: entry.label, hours: entry.hours });
787
+ }
788
+ }
789
+ const activeGroups = groups.filter((g) => g.hours && g.hours !== "Closed");
790
+ if (activeGroups.length === 0)
791
+ return "Closed";
792
+ return activeGroups
793
+ .map((g) => (g.first === g.last ? `${g.first}: ${g.hours}` : `${g.first} – ${g.last}: ${g.hours}`))
794
+ .join(", ");
795
+ }
721
796
  function normalizeProductItem(p) {
722
797
  if (!isRecord(p))
723
798
  return p;
@@ -791,7 +866,9 @@ function normalizeProductItem(p) {
791
866
  productName: (titleCandidate ?? nameCandidate),
792
867
  itemTitle: (titleCandidate ?? nameCandidate),
793
868
  minPrice: explicitMin !== undefined ? explicitMin : undefined,
869
+ priceMin: (explicitMin ?? p.priceMin) !== undefined ? (explicitMin ?? p.priceMin) : undefined,
794
870
  maxPrice: explicitMax !== undefined ? explicitMax : undefined,
871
+ priceMax: (explicitMax ?? p.priceMax) !== undefined ? (explicitMax ?? p.priceMax) : undefined,
795
872
  priceRange: explicitRange !== undefined ? explicitRange : undefined,
796
873
  isPriceRange: isRange,
797
874
  ...(numPrice !== undefined
package/dist/index.d.ts CHANGED
@@ -5,12 +5,15 @@
5
5
  export * from './PreviewField';
6
6
  export * from './EditableText';
7
7
  export * from './EditableImage';
8
+ export * from './EditableIcon';
8
9
  export * from './EditableMap';
9
10
  export * from './EditableList';
10
11
  export * from './EditableBox';
11
12
  export * from './EditableGrid';
12
13
  export * from './EditableSection';
13
14
  export * from './EditableProductCard';
15
+ export * from './EditableVehicleCard';
16
+ export * from './EditableUniversalCard';
14
17
  export * from './EditableProductGrid';
15
18
  export * from './EditableProductShowcase';
16
19
  export * from './EditableProductDetail';
@@ -72,6 +75,8 @@ export { EditableSection as Section } from './EditableSection';
72
75
  export { EditableBox as Box } from './EditableBox';
73
76
  export { EditableList as List } from './EditableList';
74
77
  export { EditableProductCard as ProductCard } from './EditableProductCard';
78
+ export { EditableVehicleCard as VehicleCard } from './EditableVehicleCard';
79
+ export { EditableUniversalCard as UniversalCard, EditableUniversalCard as ListingCard, EditableUniversalCard as EntityCard } from './EditableUniversalCard';
75
80
  export { EditableProductGrid as ProductGrid } from './EditableProductGrid';
76
81
  export { EditableProductShowcase as ProductShowcase } from './EditableProductShowcase';
77
82
  export { EditableProductDetail as ProductDetail } from './EditableProductDetail';
package/dist/index.js CHANGED
@@ -18,17 +18,20 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
18
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.FAQ = exports.Accordion = exports.Testimonials = exports.TestimonialSection = exports.TestimonialCard = exports.PricingCard = exports.ServiceCard = exports.GoogleFeedback = exports.CustomerReviews = exports.ProductDetail = exports.ProductShowcase = exports.ProductGrid = exports.ProductCard = exports.List = exports.Box = exports.Section = exports.Grid = exports.Map = exports.Image = exports.Quote = exports.Badge = exports.Paragraph = exports.Heading = exports.Text = exports.Dialog = exports.Card = exports.Button = exports.collectStyleTargetsFromHtml = exports.validateStyleTree = exports.styleToCssVariables = exports.patchStyleByPath = exports.patchElementStyle = exports.STYLE_TYPE_ATTRIBUTE = exports.STYLE_TARGET_ATTRIBUTE = exports.DENEB_STYLE_PATCH_MESSAGE = exports.STYLE_PATCH_MESSAGE = exports.formatResponsiveFontSize = exports.resolveInstallableFont = exports.normalizeFontId = exports.lookupFontDefinition = exports.listFontsByCategory = exports.collectFontIdsFromSiteData = exports.buildGoogleFontsStylesheetUrl = exports.DENEB_GOOGLE_FONT_COUNT = exports.DENEB_FONT_REGISTRY = exports.useDenebFonts = exports.DENEB_FONTS_LINK_ID = exports.FontLoader = exports.DenebComponentStyles = exports.ResponsiveBaseStyles = void 0;
22
- exports.generateThemeVariables = exports.mergeVisualCustomizationIntoTheme = exports.customizationToTheme = exports.parseVisualCustomization = exports.normalizeHexColor = exports.toHexColor = exports.getAutoContrastTextColor = exports.isDarkColor = exports.FONT_PAIRINGS = exports.GLOBAL_FONT_OPTIONS = exports.THEME_PALETTES = exports.DENEB_AUTHOR = exports.DENEB_FRAMEWORK_VERSION = exports.DENEB_FRAMEWORK_NAME = exports.AdditionalPagesNav = exports.PlatformAdditionalPages = exports.TestimonialCarousel = exports.BookingModal = exports.BeforeAfterSlider = exports.FilterSidebar = exports.CartDrawer = exports.CategoryPills = exports.AnnouncementBar = exports.HeroSplit = exports.Hero = exports.Footer = exports.Header = exports.Navbar = exports.ContactForm = void 0;
21
+ exports.TestimonialCard = exports.PricingCard = exports.ServiceCard = exports.GoogleFeedback = exports.CustomerReviews = exports.ProductDetail = exports.ProductShowcase = exports.ProductGrid = exports.EntityCard = exports.ListingCard = exports.UniversalCard = exports.VehicleCard = exports.ProductCard = exports.List = exports.Box = exports.Section = exports.Grid = exports.Map = exports.Image = exports.Quote = exports.Badge = exports.Paragraph = exports.Heading = exports.Text = exports.Dialog = exports.Card = exports.Button = exports.collectStyleTargetsFromHtml = exports.validateStyleTree = exports.styleToCssVariables = exports.patchStyleByPath = exports.patchElementStyle = exports.STYLE_TYPE_ATTRIBUTE = exports.STYLE_TARGET_ATTRIBUTE = exports.DENEB_STYLE_PATCH_MESSAGE = exports.STYLE_PATCH_MESSAGE = exports.formatResponsiveFontSize = exports.resolveInstallableFont = exports.normalizeFontId = exports.lookupFontDefinition = exports.listFontsByCategory = exports.collectFontIdsFromSiteData = exports.buildGoogleFontsStylesheetUrl = exports.DENEB_GOOGLE_FONT_COUNT = exports.DENEB_FONT_REGISTRY = exports.useDenebFonts = exports.DENEB_FONTS_LINK_ID = exports.FontLoader = exports.DenebComponentStyles = exports.ResponsiveBaseStyles = void 0;
22
+ exports.generateThemeVariables = exports.mergeVisualCustomizationIntoTheme = exports.customizationToTheme = exports.parseVisualCustomization = exports.normalizeHexColor = exports.toHexColor = exports.getAutoContrastTextColor = exports.isDarkColor = exports.FONT_PAIRINGS = exports.GLOBAL_FONT_OPTIONS = exports.THEME_PALETTES = exports.DENEB_AUTHOR = exports.DENEB_FRAMEWORK_VERSION = exports.DENEB_FRAMEWORK_NAME = exports.AdditionalPagesNav = exports.PlatformAdditionalPages = exports.TestimonialCarousel = exports.BookingModal = exports.BeforeAfterSlider = exports.FilterSidebar = exports.CartDrawer = exports.CategoryPills = exports.AnnouncementBar = exports.HeroSplit = exports.Hero = exports.Footer = exports.Header = exports.Navbar = exports.ContactForm = exports.FAQ = exports.Accordion = exports.Testimonials = exports.TestimonialSection = void 0;
23
23
  __exportStar(require("./PreviewField"), exports);
24
24
  __exportStar(require("./EditableText"), exports);
25
25
  __exportStar(require("./EditableImage"), exports);
26
+ __exportStar(require("./EditableIcon"), exports);
26
27
  __exportStar(require("./EditableMap"), exports);
27
28
  __exportStar(require("./EditableList"), exports);
28
29
  __exportStar(require("./EditableBox"), exports);
29
30
  __exportStar(require("./EditableGrid"), exports);
30
31
  __exportStar(require("./EditableSection"), exports);
31
32
  __exportStar(require("./EditableProductCard"), exports);
33
+ __exportStar(require("./EditableVehicleCard"), exports);
34
+ __exportStar(require("./EditableUniversalCard"), exports);
32
35
  __exportStar(require("./EditableProductGrid"), exports);
33
36
  __exportStar(require("./EditableProductShowcase"), exports);
34
37
  __exportStar(require("./EditableProductDetail"), exports);
@@ -130,6 +133,12 @@ var EditableList_1 = require("./EditableList");
130
133
  Object.defineProperty(exports, "List", { enumerable: true, get: function () { return EditableList_1.EditableList; } });
131
134
  var EditableProductCard_1 = require("./EditableProductCard");
132
135
  Object.defineProperty(exports, "ProductCard", { enumerable: true, get: function () { return EditableProductCard_1.EditableProductCard; } });
136
+ var EditableVehicleCard_1 = require("./EditableVehicleCard");
137
+ Object.defineProperty(exports, "VehicleCard", { enumerable: true, get: function () { return EditableVehicleCard_1.EditableVehicleCard; } });
138
+ var EditableUniversalCard_1 = require("./EditableUniversalCard");
139
+ Object.defineProperty(exports, "UniversalCard", { enumerable: true, get: function () { return EditableUniversalCard_1.EditableUniversalCard; } });
140
+ Object.defineProperty(exports, "ListingCard", { enumerable: true, get: function () { return EditableUniversalCard_1.EditableUniversalCard; } });
141
+ Object.defineProperty(exports, "EntityCard", { enumerable: true, get: function () { return EditableUniversalCard_1.EditableUniversalCard; } });
133
142
  var EditableProductGrid_1 = require("./EditableProductGrid");
134
143
  Object.defineProperty(exports, "ProductGrid", { enumerable: true, get: function () { return EditableProductGrid_1.EditableProductGrid; } });
135
144
  var EditableProductShowcase_1 = require("./EditableProductShowcase");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/ui",
3
- "version": "2.0.111",
3
+ "version": "2.0.113",
4
4
  "description": "Visual-first React component library for editable commerce storefronts. Built for Next.js and Fivora.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  ],
51
51
  "license": "MIT",
52
52
  "dependencies": {
53
- "@deneb-ui/core": "^2.0.111"
53
+ "@deneb-ui/core": "^2.0.113"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.0.0 || ^19.0.0",