@deneb-ui/ui 2.0.0

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.
Files changed (84) hide show
  1. package/README.md +182 -0
  2. package/dist/EditableBox.d.ts +56 -0
  3. package/dist/EditableBox.js +113 -0
  4. package/dist/EditableCard.d.ts +48 -0
  5. package/dist/EditableCard.js +85 -0
  6. package/dist/EditableContactForm.d.ts +11 -0
  7. package/dist/EditableContactForm.js +146 -0
  8. package/dist/EditableDialog.d.ts +25 -0
  9. package/dist/EditableDialog.js +145 -0
  10. package/dist/EditableFAQAccordion.d.ts +19 -0
  11. package/dist/EditableFAQAccordion.js +62 -0
  12. package/dist/EditableFooter.d.ts +31 -0
  13. package/dist/EditableFooter.js +330 -0
  14. package/dist/EditableGrid.d.ts +40 -0
  15. package/dist/EditableGrid.js +39 -0
  16. package/dist/EditableHero.d.ts +27 -0
  17. package/dist/EditableHero.js +154 -0
  18. package/dist/EditableImage.d.ts +13 -0
  19. package/dist/EditableImage.js +38 -0
  20. package/dist/EditableList.d.ts +33 -0
  21. package/dist/EditableList.js +19 -0
  22. package/dist/EditableNavbar.d.ts +28 -0
  23. package/dist/EditableNavbar.js +178 -0
  24. package/dist/EditablePricingCard.d.ts +31 -0
  25. package/dist/EditablePricingCard.js +93 -0
  26. package/dist/EditableProductCard.d.ts +60 -0
  27. package/dist/EditableProductCard.js +116 -0
  28. package/dist/EditableSection.d.ts +35 -0
  29. package/dist/EditableSection.js +34 -0
  30. package/dist/EditableServiceCard.d.ts +38 -0
  31. package/dist/EditableServiceCard.js +28 -0
  32. package/dist/EditableTestimonialCard.d.ts +17 -0
  33. package/dist/EditableTestimonialCard.js +46 -0
  34. package/dist/EditableText.d.ts +117 -0
  35. package/dist/EditableText.js +319 -0
  36. package/dist/SiteDataProvider.d.ts +61 -0
  37. package/dist/SiteDataProvider.js +316 -0
  38. package/dist/ThemeStyles.d.ts +46 -0
  39. package/dist/ThemeStyles.js +181 -0
  40. package/dist/business/BusinessHours.d.ts +20 -0
  41. package/dist/business/BusinessHours.js +109 -0
  42. package/dist/business/index.d.ts +1 -0
  43. package/dist/business/index.js +17 -0
  44. package/dist/contact/ContactActions.d.ts +24 -0
  45. package/dist/contact/ContactActions.js +30 -0
  46. package/dist/contact/ContactButton.d.ts +15 -0
  47. package/dist/contact/ContactButton.js +94 -0
  48. package/dist/contact/EmailButton.d.ts +6 -0
  49. package/dist/contact/EmailButton.js +12 -0
  50. package/dist/contact/PhoneButton.d.ts +6 -0
  51. package/dist/contact/PhoneButton.js +12 -0
  52. package/dist/contact/WhatsAppButton.d.ts +6 -0
  53. package/dist/contact/WhatsAppButton.js +13 -0
  54. package/dist/contact/index.d.ts +5 -0
  55. package/dist/contact/index.js +21 -0
  56. package/dist/core/DenebAction.d.ts +19 -0
  57. package/dist/core/DenebAction.js +107 -0
  58. package/dist/core/index.d.ts +1 -0
  59. package/dist/core/index.js +17 -0
  60. package/dist/index.d.ts +61 -0
  61. package/dist/index.js +106 -0
  62. package/dist/location/Address.d.ts +14 -0
  63. package/dist/location/Address.js +26 -0
  64. package/dist/location/LocationCard.d.ts +20 -0
  65. package/dist/location/LocationCard.js +41 -0
  66. package/dist/location/LocationLink.d.ts +18 -0
  67. package/dist/location/LocationLink.js +23 -0
  68. package/dist/location/MapEmbed.d.ts +18 -0
  69. package/dist/location/MapEmbed.js +30 -0
  70. package/dist/location/MapLink.d.ts +13 -0
  71. package/dist/location/MapLink.js +63 -0
  72. package/dist/location/index.d.ts +5 -0
  73. package/dist/location/index.js +21 -0
  74. package/dist/social/SocialButton.d.ts +11 -0
  75. package/dist/social/SocialButton.js +112 -0
  76. package/dist/social/SocialLinks.d.ts +14 -0
  77. package/dist/social/SocialLinks.js +42 -0
  78. package/dist/social/index.d.ts +2 -0
  79. package/dist/social/index.js +18 -0
  80. package/dist/utils/index.d.ts +1 -0
  81. package/dist/utils/index.js +17 -0
  82. package/dist/utils/urls.d.ts +32 -0
  83. package/dist/utils/urls.js +91 -0
  84. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # DENEB
2
+
3
+ ### A modern UI system for the web.
4
+
5
+ > **DENEB UI — Build beautiful interfaces, effortlessly.**
6
+ > The premier visual-first React component ecosystem.
7
+ > Created by **Chamika Gayashan (CEE G)**.
8
+
9
+ [![Framework: DENEB UI](https://img.shields.io/badge/Framework-DENEB_UI_v2.0-blue.svg)](https://github.com/chamikathereal)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-orange.svg)](https://opensource.org/licenses/MIT)
11
+
12
+ ---
13
+
14
+ ## Quick Installation
15
+
16
+ ```bash
17
+ npm install @deneb-ui/ui
18
+ # Or with yarn / pnpm
19
+ pnpm add @deneb-ui/ui
20
+ ```
21
+
22
+ ```tsx
23
+ import {
24
+ Button,
25
+ Card,
26
+ Dialog,
27
+ ContactActions,
28
+ WhatsAppButton,
29
+ LocationCard,
30
+ SocialLinks,
31
+ BusinessHours,
32
+ } from "@deneb-ui/ui";
33
+ ```
34
+
35
+ ---
36
+
37
+ ## What is DENEB UI?
38
+
39
+ **DENEB UI** is a modern, modular design system and component framework engineered specifically for live visual editing, dynamic theming, and high-converting commerce storefronts.
40
+
41
+ Every component in DENEB UI:
42
+ - **Zero-Breakage Visual Editing**: Automatically connects to the bidirectional live editor protocol (`DENEB_PREVIEW_SITE_DATA` / `FIVORA_PREVIEW_SITE_DATA`, `DENEB_PREVIEW_READY`, `DENEB_PREVIEW_FOCUS_PAGE`).
43
+ - **Auto-Balancing Layouts**: Columns and card heights automatically adapt across desktop, tablet, and mobile regardless of content length.
44
+ - **Smart Contact & Location Actions**: One-click WhatsApp, call, email, and Google Maps generation from raw phone numbers and addresses.
45
+ - **Dynamic Tokenized Theming**: Fully integrated with tokenized custom properties (`--color-primary`, `--color-secondary`, fonts, spacing) and AI-assisted restyling.
46
+ - **Selected-Page Route Guard**: Automatically respects `requirements.requiredPages`, ensuring zero dead links or validation warnings.
47
+
48
+ ---
49
+
50
+ ## Component Catalog
51
+
52
+ ### 1. Smart Template & Action Components
53
+ - `<ContactActions />` — Auto-detects and renders active communication channels (`phone`, `whatsapp`, `email`). When a merchant adds a phone number in `site-data.json`, the button appears automatically with zero template changes.
54
+ - `<WhatsAppButton />` — One-click WhatsApp click-to-chat button with built-in SVG icon and automatic `https://wa.me/...` URL generation from raw phone numbers.
55
+ - `<PhoneButton />` — One-click calling button with `tel:` handler and raw number editing bindings.
56
+ - `<EmailButton />` — One-click email button with `mailto:` handler and optional subject lines.
57
+ - `<ContactButton />` — Base unified action button supporting WhatsApp, phone, email, and forms.
58
+ - `<LocationCard />` — High-converting location card with pin icon, formatted address, and Google Maps directions button.
59
+ - `<LocationLink />` — Semantic address display paired with a "Get Directions" action.
60
+ - `<MapLink />` — Direct link button to open Google Maps directions or search queries.
61
+ - `<MapEmbed />` — Responsive map embed iframe with safe graceful fallback to `MapLink` when embed URLs are unconfigured.
62
+ - `<Address />` — Structured `<address>` component with individual street, locality, and country field markers.
63
+ - `<SocialLinks />` — Smart social media container that filters out unconfigured platforms automatically.
64
+ - `<SocialButton />` — Brand-colored social button with built-in SVGs (Instagram, Facebook, TikTok, YouTube, LinkedIn, X, Twitter, Pinterest, GitHub).
65
+ - `<BusinessHours />` — Weekly operating schedule renderer with a dynamic live **Open Now** / **Closed** status badge.
66
+ - `<DenebAction />` (or `<ActionLink />`) — Universal action primitive connecting action types, URL generation, and visual editing markers.
67
+
68
+ ### 2. Interactive Primitives
69
+ - `<Button />` (or `<EditableButton />`) — Modern button with `primary`, `secondary`, `accent`, `glass`, `ghost` styles and `targetPage` guard.
70
+ - `<Dialog />` (or `<EditableDialog />`) — Accessible popup modal with backdrop blur, smooth entrance, keyboard ESC exit, title, description, and footer actions.
71
+ - `<Card />` (or `<EditableCard />`) — Flexible surface card with auto-balancing content layout.
72
+ - `<Text />`, `<Heading />`, `<Paragraph />` — Typography components with responsive sizing and live editor binding.
73
+ - `<Badge />` — Status indicators, tags, and promotional pills (`primary`, `secondary`, `accent`, `glass`, `glow`).
74
+ - `<Link />` — Route link that automatically validates against `requirements.requiredPages`.
75
+ - `<Quote />` — Editorial pull quotes and callouts.
76
+
77
+ ### 3. Sections & Commerce
78
+ - `<ProductCard />` — 4 visual variants (`modern-glass`, `classic`, `minimal`, `horizontal`) with discount tags and pricing.
79
+ - `<PricingCard />` — Subscription/pricing tiers with feature checklist.
80
+ - `<TestimonialCard />` — Customer reviews with 1–5 stars and author avatar.
81
+ - `<ContactForm />` — Direct submission form connected to shared API endpoint.
82
+ - `<Navbar />` — Sticky header with logo, mobile drawer, and filtered navigation.
83
+ - `<Footer />` — Responsive footer with copyright and route filtering.
84
+ - `<Hero />` & `<HeroSplit />` — High-impact hero sections with eyebrow badges and dual CTAs.
85
+ - `<Grid />` — Auto-balancing CSS grid with equal height.
86
+ - `<Accordion />` / `<FAQ />` — Collapsible Q&A list.
87
+ - `<Image />` — Aspect ratio locked image with SVG fallback placeholder.
88
+
89
+ ---
90
+
91
+ ## URL Utility Layer
92
+
93
+ DENEB UI exports a dedicated utility suite for URL formatting and generation:
94
+
95
+ ```tsx
96
+ import {
97
+ createWhatsAppUrl,
98
+ createPhoneUrl,
99
+ createEmailUrl,
100
+ createMapUrl,
101
+ isSafeExternalLink,
102
+ } from "@deneb-ui/ui";
103
+
104
+ // Generates: https://wa.me/94771234567?text=Hello%20Store
105
+ const waUrl = createWhatsAppUrl("+94 77 123 4567", "Hello Store");
106
+
107
+ // Generates: tel:+94112345678
108
+ const telUrl = createPhoneUrl("+94 11 234 5678");
109
+
110
+ // Generates: mailto:hello@store.com?subject=Enquiry
111
+ const mailUrl = createEmailUrl("hello@store.com", "Enquiry");
112
+
113
+ // Generates Google Maps directions search URL
114
+ const mapUrl = createMapUrl({ address: "123 Main Street", city: "Colombo", country: "Sri Lanka" });
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Example Usage
120
+
121
+ ```tsx
122
+ import {
123
+ SiteDataProvider,
124
+ Navbar,
125
+ Hero,
126
+ ProductCard,
127
+ ContactActions,
128
+ LocationCard,
129
+ BusinessHours,
130
+ SocialLinks,
131
+ Footer,
132
+ useSiteData,
133
+ } from '@deneb-ui/ui';
134
+
135
+ import siteData from '@/data/site-data.json';
136
+
137
+ export default function HomePage() {
138
+ const data = useSiteData();
139
+ const business = data.content?.common?.business || {};
140
+
141
+ return (
142
+ <SiteDataProvider initialSiteData={siteData}>
143
+ <Navbar />
144
+
145
+ <main data-preview-page-key="home">
146
+ <Hero />
147
+
148
+ {/* Smart Contact & Actions */}
149
+ <section className="py-12 max-w-5xl mx-auto px-6 flex flex-col items-center gap-6">
150
+ <ContactActions
151
+ phone={business.phone}
152
+ whatsapp={business.whatsapp}
153
+ email={business.email}
154
+ />
155
+
156
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full mt-6">
157
+ <LocationCard
158
+ address={business.location?.address}
159
+ city={business.location?.city}
160
+ country={business.location?.country}
161
+ mapUrl={business.location?.mapUrl}
162
+ />
163
+
164
+ <BusinessHours hours={business.hours} />
165
+ </div>
166
+
167
+ <SocialLinks social={business.social} />
168
+ </section>
169
+ </main>
170
+
171
+ <Footer />
172
+ </SiteDataProvider>
173
+ );
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Author & Credits
180
+
181
+ Created and architected by **Chamika Gayashan (CEE G)**.
182
+ Built for the global React and storefront developer ecosystem.
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ export type BoxRadius = 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | (string & {}) | number;
3
+ export type BoxShadow = 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | (string & {});
4
+ export type BoxSpacing = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | (string & {}) | number;
5
+ export type BoxAspectRatio = '1/1' | '16/9' | '4/3' | '3/4' | '9/16' | '2/3' | '3/2' | 'square' | 'video' | 'portrait' | 'landscape' | (string & {});
6
+ export interface EditableBoxProps extends React.HTMLAttributes<HTMLElement> {
7
+ as?: React.ElementType;
8
+ /**
9
+ * Field path or item path in siteData.
10
+ */
11
+ id?: string;
12
+ 'data-preview-field-path'?: string;
13
+ 'data-preview-item-path'?: string;
14
+ 'data-preview-static'?: string;
15
+ /**
16
+ * Sizing controls
17
+ */
18
+ width?: string | number;
19
+ minWidth?: string | number;
20
+ maxWidth?: string | number;
21
+ height?: string | number;
22
+ minHeight?: string | number;
23
+ maxHeight?: string | number;
24
+ aspectRatio?: BoxAspectRatio;
25
+ /**
26
+ * Auto-balance: expands and balances item within flex/grid rows to match available space evenly.
27
+ */
28
+ balance?: boolean;
29
+ grow?: boolean | number;
30
+ shrink?: boolean | number;
31
+ /**
32
+ * Spacing & styling
33
+ */
34
+ padding?: BoxSpacing;
35
+ margin?: BoxSpacing;
36
+ bg?: string;
37
+ color?: string;
38
+ radius?: BoxRadius;
39
+ shadow?: BoxShadow;
40
+ border?: string | boolean;
41
+ /**
42
+ * Visual layout & alignment
43
+ */
44
+ display?: 'block' | 'flex' | 'grid' | 'inline-block' | 'inline-flex';
45
+ direction?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
46
+ wrap?: boolean | 'wrap' | 'nowrap' | 'wrap-reverse';
47
+ align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
48
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
49
+ gap?: BoxSpacing;
50
+ overflow?: 'visible' | 'hidden' | 'auto' | 'scroll';
51
+ }
52
+ /**
53
+ * EditableBox is a flexible, smart container ("div-like") component with built-in
54
+ * width/height resizing, aspect-ratio controls, and auto-balancing for fivora cards.
55
+ */
56
+ export declare function EditableBox({ as: Component, id, 'data-preview-field-path': previewFieldPath, 'data-preview-item-path': previewItemPath, 'data-preview-static': previewStatic, width, minWidth, maxWidth, height, minHeight, maxHeight, aspectRatio, balance, grow, shrink, padding, margin, bg, color, radius, shadow, border, display, direction, wrap, align, justify, gap, overflow, style, children, className, ...props }: EditableBoxProps): React.JSX.Element;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EditableBox = EditableBox;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const SiteDataProvider_1 = require("./SiteDataProvider");
6
+ const RADIUS_MAP = {
7
+ none: '0px',
8
+ sm: '4px',
9
+ md: '8px',
10
+ lg: '12px',
11
+ xl: '16px',
12
+ '2xl': '24px',
13
+ full: '9999px',
14
+ };
15
+ const SHADOW_MAP = {
16
+ none: 'none',
17
+ sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
18
+ md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
19
+ lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
20
+ xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
21
+ '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
22
+ };
23
+ const SPACING_MAP = {
24
+ none: '0',
25
+ xs: '0.25rem',
26
+ sm: '0.5rem',
27
+ md: '1rem',
28
+ lg: '1.5rem',
29
+ xl: '2rem',
30
+ '2xl': '3rem',
31
+ };
32
+ const ASPECT_MAP = {
33
+ square: '1 / 1',
34
+ '1/1': '1 / 1',
35
+ video: '16 / 9',
36
+ '16/9': '16 / 9',
37
+ portrait: '3 / 4',
38
+ '3/4': '3 / 4',
39
+ '4/3': '4 / 3',
40
+ landscape: '4 / 3',
41
+ '9/16': '9 / 16',
42
+ '2/3': '2 / 3',
43
+ '3/2': '3 / 2',
44
+ };
45
+ /**
46
+ * EditableBox is a flexible, smart container ("div-like") component with built-in
47
+ * width/height resizing, aspect-ratio controls, and auto-balancing for fivora cards.
48
+ */
49
+ function EditableBox({ as: Component = 'div', id, 'data-preview-field-path': previewFieldPath, 'data-preview-item-path': previewItemPath, 'data-preview-static': previewStatic, width, minWidth, maxWidth, height, minHeight, maxHeight, aspectRatio, balance, grow, shrink, padding, margin, bg, color, radius, shadow, border, display, direction, wrap, align, justify, gap, overflow, style, children, className = '', ...props }) {
50
+ const resolvedRadius = radius !== undefined ? (RADIUS_MAP[String(radius)] || String(radius)) : undefined;
51
+ const resolvedShadow = shadow ? (SHADOW_MAP[shadow] || shadow) : undefined;
52
+ const resolvedPadding = padding !== undefined ? (SPACING_MAP[String(padding)] || String(padding)) : undefined;
53
+ const resolvedMargin = margin !== undefined ? (SPACING_MAP[String(margin)] || String(margin)) : undefined;
54
+ const resolvedGap = gap !== undefined ? (SPACING_MAP[String(gap)] || String(gap)) : undefined;
55
+ const resolvedAspect = aspectRatio ? (ASPECT_MAP[aspectRatio] || aspectRatio) : undefined;
56
+ const resolvedAlign = align === 'start' ? 'flex-start' : align === 'end' ? 'flex-end' : align;
57
+ const resolvedJustify = justify === 'start'
58
+ ? 'flex-start'
59
+ : justify === 'end'
60
+ ? 'flex-end'
61
+ : justify === 'between'
62
+ ? 'space-between'
63
+ : justify === 'around'
64
+ ? 'space-around'
65
+ : justify === 'evenly'
66
+ ? 'space-evenly'
67
+ : justify;
68
+ const resolvedWrap = typeof wrap === 'boolean' ? (wrap ? 'wrap' : 'nowrap') : wrap;
69
+ const resolvedBorder = border === true
70
+ ? '1px solid var(--border-color, rgba(226, 232, 240, 0.8))'
71
+ : typeof border === 'string'
72
+ ? border
73
+ : undefined;
74
+ const path = previewFieldPath || id || previewItemPath;
75
+ const dynamicStyle = (0, SiteDataProvider_1.useFieldStyle)(path);
76
+ const dynamicMarginTop = dynamicStyle?.spacingTop !== undefined && dynamicStyle?.spacingTop !== '' ? `${dynamicStyle.spacingTop}px` : undefined;
77
+ const dynamicMarginBottom = dynamicStyle?.spacingBottom !== undefined && dynamicStyle?.spacingBottom !== '' ? `${dynamicStyle.spacingBottom}px` : undefined;
78
+ const dynamicMarginLeft = dynamicStyle?.spacingLeft !== undefined && dynamicStyle?.spacingLeft !== '' ? `${dynamicStyle.spacingLeft}px` : undefined;
79
+ const dynamicMarginRight = dynamicStyle?.spacingRight !== undefined && dynamicStyle?.spacingRight !== '' ? `${dynamicStyle.spacingRight}px` : undefined;
80
+ const boxStyle = {
81
+ ...(width !== undefined ? { width } : {}),
82
+ ...(minWidth !== undefined ? { minWidth } : {}),
83
+ ...(maxWidth !== undefined ? { maxWidth } : {}),
84
+ ...(height !== undefined ? { height } : {}),
85
+ ...(minHeight !== undefined ? { minHeight } : {}),
86
+ ...(maxHeight !== undefined ? { maxHeight } : {}),
87
+ ...(resolvedAspect ? { aspectRatio: resolvedAspect } : {}),
88
+ ...(balance ? { flex: '1 1 0%', minWidth: 0, height: '100%' } : {}),
89
+ ...(grow !== undefined ? { flexGrow: typeof grow === 'boolean' ? (grow ? 1 : 0) : grow } : {}),
90
+ ...(shrink !== undefined ? { flexShrink: typeof shrink === 'boolean' ? (shrink ? 1 : 0) : shrink } : {}),
91
+ ...(bg ? { background: bg } : {}),
92
+ ...(color ? { color } : {}),
93
+ ...(resolvedRadius ? { borderRadius: resolvedRadius } : {}),
94
+ ...(resolvedShadow ? { boxShadow: resolvedShadow } : {}),
95
+ ...(resolvedBorder ? { border: resolvedBorder } : {}),
96
+ ...(resolvedPadding ? { padding: resolvedPadding } : {}),
97
+ ...(resolvedMargin ? { margin: resolvedMargin } : {}),
98
+ ...(display ? { display } : {}),
99
+ ...(direction ? { flexDirection: direction } : {}),
100
+ ...(resolvedWrap ? { flexWrap: resolvedWrap } : {}),
101
+ ...(resolvedAlign ? { alignItems: resolvedAlign } : {}),
102
+ ...(resolvedJustify ? { justifyContent: resolvedJustify } : {}),
103
+ ...(resolvedGap ? { gap: resolvedGap } : {}),
104
+ ...(overflow ? { overflow } : {}),
105
+ ...style,
106
+ ...(dynamicMarginTop ? { marginTop: dynamicMarginTop } : {}),
107
+ ...(dynamicMarginBottom ? { marginBottom: dynamicMarginBottom } : {}),
108
+ ...(dynamicMarginLeft ? { marginLeft: dynamicMarginLeft } : {}),
109
+ ...(dynamicMarginRight ? { marginRight: dynamicMarginRight } : {}),
110
+ ...(dynamicStyle?.color ? { color: String(dynamicStyle.color) } : {}),
111
+ };
112
+ return ((0, jsx_runtime_1.jsx)(Component, { "data-preview-field-path": previewFieldPath || id, "data-preview-item-path": previewItemPath, "data-preview-static": previewStatic, className: `editable-box ${className}`.trim(), style: boxStyle, ...props, children: children }));
113
+ }
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { BoxAspectRatio, BoxRadius, BoxShadow, BoxSpacing } from './EditableBox';
3
+ export interface CardItem {
4
+ id?: string | number;
5
+ title?: string;
6
+ name?: string;
7
+ body?: string;
8
+ description?: string;
9
+ imageUrl?: string;
10
+ [key: string]: unknown;
11
+ }
12
+ export interface EditableCardProps extends React.HTMLAttributes<HTMLElement> {
13
+ itemPath: string;
14
+ item: CardItem;
15
+ titleKey?: string;
16
+ bodyKey?: string;
17
+ imageKey?: string;
18
+ imageFallback?: string;
19
+ as?: React.ElementType;
20
+ /**
21
+ * Card sizing and dimension controls
22
+ */
23
+ width?: string | number;
24
+ minWidth?: string | number;
25
+ maxWidth?: string | number;
26
+ height?: string | number;
27
+ minHeight?: string | number;
28
+ maxHeight?: string | number;
29
+ aspectRatio?: BoxAspectRatio;
30
+ /**
31
+ * Auto-balance: ensures card stretches evenly in flex/grid to match adjacent cards.
32
+ */
33
+ balance?: boolean;
34
+ /**
35
+ * Styling controls
36
+ */
37
+ radius?: BoxRadius;
38
+ shadow?: BoxShadow;
39
+ bg?: string;
40
+ border?: string | boolean;
41
+ padding?: BoxSpacing;
42
+ align?: 'left' | 'center' | 'right';
43
+ }
44
+ /**
45
+ * Reusable card for services, features, or list items across pages with
46
+ * responsive width/height sizing and auto-balancing space support.
47
+ */
48
+ export declare function EditableCard({ itemPath, item, titleKey, bodyKey, imageKey, imageFallback, as: Component, width, minWidth, maxWidth, height, minHeight, maxHeight, aspectRatio, balance, radius, shadow, bg, border, padding, align, className, style, children, ...props }: EditableCardProps): React.JSX.Element;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EditableCard = EditableCard;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const EditableText_1 = require("./EditableText");
6
+ const EditableImage_1 = require("./EditableImage");
7
+ const RADIUS_MAP = {
8
+ none: '0px',
9
+ sm: '4px',
10
+ md: '8px',
11
+ lg: '12px',
12
+ xl: '16px',
13
+ '2xl': '24px',
14
+ full: '9999px',
15
+ };
16
+ const SHADOW_MAP = {
17
+ none: 'none',
18
+ sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
19
+ md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
20
+ lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
21
+ xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
22
+ '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
23
+ };
24
+ const SPACING_MAP = {
25
+ none: '0',
26
+ xs: '0.25rem',
27
+ sm: '0.5rem',
28
+ md: '1rem',
29
+ lg: '1.5rem',
30
+ xl: '2rem',
31
+ '2xl': '3rem',
32
+ };
33
+ const ASPECT_MAP = {
34
+ square: '1 / 1',
35
+ '1/1': '1 / 1',
36
+ video: '16 / 9',
37
+ '16/9': '16 / 9',
38
+ portrait: '3 / 4',
39
+ '3/4': '3 / 4',
40
+ '4/3': '4 / 3',
41
+ landscape: '4 / 3',
42
+ };
43
+ /**
44
+ * Reusable card for services, features, or list items across pages with
45
+ * responsive width/height sizing and auto-balancing space support.
46
+ */
47
+ function EditableCard({ itemPath, item, titleKey = 'title', bodyKey = 'body', imageKey = 'imageUrl', imageFallback = '/placeholder.svg', as: Component = 'article', width, minWidth, maxWidth, height, minHeight, maxHeight, aspectRatio, balance, radius, shadow, bg, border, padding, align, className = '', style, children, ...props }) {
48
+ const title = String(item?.[titleKey] ?? item?.name ?? '');
49
+ const body = String(item?.[bodyKey] ?? item?.description ?? '');
50
+ const imageUrl = item?.[imageKey] !== undefined ? String(item[imageKey]) : undefined;
51
+ const resolvedRadius = radius !== undefined ? (RADIUS_MAP[String(radius)] || String(radius)) : undefined;
52
+ const resolvedShadow = shadow ? (SHADOW_MAP[shadow] || shadow) : undefined;
53
+ const resolvedPadding = padding !== undefined ? (SPACING_MAP[String(padding)] || String(padding)) : undefined;
54
+ const resolvedAspect = aspectRatio ? (ASPECT_MAP[aspectRatio] || aspectRatio) : undefined;
55
+ const resolvedBorder = border === true
56
+ ? '1px solid var(--border-color, rgba(226, 232, 240, 0.8))'
57
+ : typeof border === 'string'
58
+ ? border
59
+ : undefined;
60
+ const cardStyle = {
61
+ ...(width !== undefined ? { width } : {}),
62
+ ...(minWidth !== undefined ? { minWidth } : {}),
63
+ ...(maxWidth !== undefined ? { maxWidth } : {}),
64
+ ...(height !== undefined ? { height } : {}),
65
+ ...(minHeight !== undefined ? { minHeight } : {}),
66
+ ...(maxHeight !== undefined ? { maxHeight } : {}),
67
+ ...(resolvedAspect ? { aspectRatio: resolvedAspect } : {}),
68
+ ...(balance
69
+ ? {
70
+ display: 'flex',
71
+ flexDirection: 'column',
72
+ height: '100%',
73
+ flex: '1 1 0%',
74
+ }
75
+ : {}),
76
+ ...(bg ? { background: bg } : {}),
77
+ ...(resolvedRadius ? { borderRadius: resolvedRadius } : {}),
78
+ ...(resolvedShadow ? { boxShadow: resolvedShadow } : {}),
79
+ ...(resolvedBorder ? { border: resolvedBorder } : {}),
80
+ ...(resolvedPadding ? { padding: resolvedPadding } : {}),
81
+ ...(align ? { textAlign: align } : {}),
82
+ ...style,
83
+ };
84
+ return ((0, jsx_runtime_1.jsxs)(Component, { "data-preview-item-path": itemPath, className: `editable-card ${className}`.trim(), style: cardStyle, ...props, children: [imageUrl !== undefined && ((0, jsx_runtime_1.jsx)("div", { className: "editable-card-image-wrap", children: (0, jsx_runtime_1.jsx)(EditableImage_1.EditableImage, { id: `${itemPath}.${imageKey}`, src: imageUrl, fallbackSrc: imageFallback, alt: title, className: "editable-card-image" }) })), (0, jsx_runtime_1.jsx)("div", { className: "editable-card-body", style: balance ? { display: 'flex', flexDirection: 'column', flex: '1 1 auto' } : undefined, children: children ? (children) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { variant: "h3", color: "heading", id: `${itemPath}.${titleKey}`, "data-preview-field-path": `${itemPath}.${titleKey}`, defaultValue: title, className: "editable-card-title" }), (0, jsx_runtime_1.jsx)(EditableText_1.EditableText, { variant: "p", color: "muted", id: `${itemPath}.${bodyKey}`, "data-preview-field-path": `${itemPath}.${bodyKey}`, defaultValue: body, className: "editable-card-body-text" })] })) })] }));
85
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ export interface EditableContactFormProps extends React.FormHTMLAttributes<HTMLFormElement> {
3
+ endpoint?: string;
4
+ formTitle?: string;
5
+ formTitlePath?: string;
6
+ submitButtonText?: string;
7
+ submitLabelPath?: string;
8
+ successMessage?: string;
9
+ showPhone?: boolean;
10
+ }
11
+ export declare function EditableContactForm({ endpoint, formTitle, formTitlePath, submitButtonText, submitLabelPath, successMessage, showPhone, className, style, ...props }: EditableContactFormProps): React.JSX.Element;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EditableContactForm = EditableContactForm;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const SiteDataProvider_1 = require("./SiteDataProvider");
7
+ function EditableContactForm({ endpoint = 'https://api.fivora.com/site-contact', formTitle, formTitlePath, submitButtonText = 'Send Message', submitLabelPath = 'contact.submitLabel', successMessage = 'Thank you! Your message has been sent successfully.', showPhone = false, className = '', style, ...props }) {
8
+ const siteData = (0, SiteDataProvider_1.useSiteData)();
9
+ const [status, setStatus] = (0, react_1.useState)('idle');
10
+ const [errorMessage, setErrorMessage] = (0, react_1.useState)('');
11
+ const contactContent = siteData?.content?.contact;
12
+ const resolvedSubmitText = String(contactContent?.submitLabel || submitButtonText);
13
+ const resolvedFormTitle = formTitle || contactContent?.formTitle;
14
+ const handleSubmit = async (e) => {
15
+ e.preventDefault();
16
+ setStatus('submitting');
17
+ setErrorMessage('');
18
+ const form = e.currentTarget;
19
+ const formData = new FormData(form);
20
+ const payload = {
21
+ projectId: siteData?.project?.id || 'demo-project',
22
+ name: formData.get('name'),
23
+ email: formData.get('email'),
24
+ phone: showPhone ? formData.get('phone') : undefined,
25
+ message: formData.get('message'),
26
+ };
27
+ try {
28
+ const res = await fetch(endpoint, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify(payload),
32
+ });
33
+ if (!res.ok) {
34
+ throw new Error(`Submission error: ${res.statusText || 'Unable to send message'}`);
35
+ }
36
+ setStatus('success');
37
+ form.reset();
38
+ }
39
+ catch (err) {
40
+ setStatus('error');
41
+ setErrorMessage(err.message || 'Something went wrong. Please try again.');
42
+ }
43
+ };
44
+ return ((0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, "data-fivora-contact-disabled": true, style: {
45
+ display: 'flex',
46
+ flexDirection: 'column',
47
+ gap: '1.25rem',
48
+ ...style,
49
+ }, className: `editable-contact-form ${className}`.trim(), ...props, children: [(resolvedFormTitle || formTitlePath) && ((0, jsx_runtime_1.jsx)("h2", { "data-preview-field-path": formTitlePath || 'contact.formTitle', style: {
50
+ fontSize: '1.5rem',
51
+ fontWeight: 700,
52
+ color: 'var(--heading-color, #0f172a)',
53
+ margin: '0 0 0.5rem 0',
54
+ }, children: resolvedFormTitle })), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "contact-name", "data-preview-static": "contact-label-name", style: {
55
+ display: 'block',
56
+ fontSize: '0.875rem',
57
+ fontWeight: 600,
58
+ marginBottom: '0.375rem',
59
+ color: 'var(--heading-color, #0f172a)',
60
+ }, children: "Your Name" }), (0, jsx_runtime_1.jsx)("input", { id: "contact-name", name: "name", type: "text", required: true, placeholder: "John Doe", style: {
61
+ width: '100%',
62
+ padding: '0.75rem 1rem',
63
+ borderRadius: '10px',
64
+ border: '1px solid #cbd5e1',
65
+ fontSize: '0.95rem',
66
+ outline: 'none',
67
+ backgroundColor: '#ffffff',
68
+ boxSizing: 'border-box',
69
+ } })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "contact-email", "data-preview-static": "contact-label-email", style: {
70
+ display: 'block',
71
+ fontSize: '0.875rem',
72
+ fontWeight: 600,
73
+ marginBottom: '0.375rem',
74
+ color: 'var(--heading-color, #0f172a)',
75
+ }, children: "Email Address" }), (0, jsx_runtime_1.jsx)("input", { id: "contact-email", name: "email", type: "email", required: true, placeholder: "john@example.com", style: {
76
+ width: '100%',
77
+ padding: '0.75rem 1rem',
78
+ borderRadius: '10px',
79
+ border: '1px solid #cbd5e1',
80
+ fontSize: '0.95rem',
81
+ outline: 'none',
82
+ backgroundColor: '#ffffff',
83
+ boxSizing: 'border-box',
84
+ } })] }), showPhone && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "contact-phone", "data-preview-static": "contact-label-phone", style: {
85
+ display: 'block',
86
+ fontSize: '0.875rem',
87
+ fontWeight: 600,
88
+ marginBottom: '0.375rem',
89
+ color: 'var(--heading-color, #0f172a)',
90
+ }, children: "Phone Number" }), (0, jsx_runtime_1.jsx)("input", { id: "contact-phone", name: "phone", type: "tel", placeholder: "+1 (555) 000-0000", style: {
91
+ width: '100%',
92
+ padding: '0.75rem 1rem',
93
+ borderRadius: '10px',
94
+ border: '1px solid #cbd5e1',
95
+ fontSize: '0.95rem',
96
+ outline: 'none',
97
+ backgroundColor: '#ffffff',
98
+ boxSizing: 'border-box',
99
+ } })] })), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "contact-message", "data-preview-static": "contact-label-message", style: {
100
+ display: 'block',
101
+ fontSize: '0.875rem',
102
+ fontWeight: 600,
103
+ marginBottom: '0.375rem',
104
+ color: 'var(--heading-color, #0f172a)',
105
+ }, children: "Message" }), (0, jsx_runtime_1.jsx)("textarea", { id: "contact-message", name: "message", rows: 5, required: true, placeholder: "Tell us how we can help...", style: {
106
+ width: '100%',
107
+ padding: '0.75rem 1rem',
108
+ borderRadius: '10px',
109
+ border: '1px solid #cbd5e1',
110
+ fontSize: '0.95rem',
111
+ outline: 'none',
112
+ backgroundColor: '#ffffff',
113
+ boxSizing: 'border-box',
114
+ fontFamily: 'inherit',
115
+ } })] }), (0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: status === 'submitting', style: {
116
+ padding: '0.875rem 1.5rem',
117
+ borderRadius: '10px',
118
+ border: 'none',
119
+ backgroundColor: 'var(--brand-color, #2563eb)',
120
+ color: '#ffffff',
121
+ fontSize: '1rem',
122
+ fontWeight: 600,
123
+ cursor: status === 'submitting' ? 'not-allowed' : 'pointer',
124
+ opacity: status === 'submitting' ? 0.7 : 1,
125
+ boxShadow: '0 4px 14px 0 rgba(37, 99, 235, 0.35)',
126
+ transition: 'all 0.2s ease',
127
+ }, children: (0, jsx_runtime_1.jsx)("span", { "data-preview-field-path": submitLabelPath, children: status === 'submitting' ? 'Sending Message...' : resolvedSubmitText }) }), status === 'success' && ((0, jsx_runtime_1.jsx)("div", { style: {
128
+ padding: '1rem',
129
+ borderRadius: '10px',
130
+ backgroundColor: 'rgba(34, 197, 94, 0.1)',
131
+ border: '1px solid rgba(34, 197, 94, 0.3)',
132
+ color: '#15803d',
133
+ fontSize: '0.925rem',
134
+ fontWeight: 500,
135
+ textAlign: 'center',
136
+ }, children: successMessage })), status === 'error' && ((0, jsx_runtime_1.jsx)("div", { style: {
137
+ padding: '1rem',
138
+ borderRadius: '10px',
139
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
140
+ border: '1px solid rgba(239, 68, 68, 0.3)',
141
+ color: '#b91c1c',
142
+ fontSize: '0.925rem',
143
+ fontWeight: 500,
144
+ textAlign: 'center',
145
+ }, children: errorMessage }))] }));
146
+ }