keystone-design-bootstrap 1.0.3

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 (182) hide show
  1. package/README.md +179 -0
  2. package/package.json +59 -0
  3. package/src/contexts/ThemeContext.tsx +34 -0
  4. package/src/contexts/index.ts +1 -0
  5. package/src/design_system/elements/IconComponent.tsx +98 -0
  6. package/src/design_system/elements/avatar/avatar-label-group.tsx +30 -0
  7. package/src/design_system/elements/avatar/avatar-profile-photo.tsx +125 -0
  8. package/src/design_system/elements/avatar/avatar.tsx +131 -0
  9. package/src/design_system/elements/avatar/base-components/avatar-add-button.tsx +34 -0
  10. package/src/design_system/elements/avatar/base-components/avatar-company-icon.tsx +26 -0
  11. package/src/design_system/elements/avatar/base-components/avatar-online-indicator.tsx +31 -0
  12. package/src/design_system/elements/avatar/base-components/index.tsx +4 -0
  13. package/src/design_system/elements/avatar/base-components/verified-tick.tsx +34 -0
  14. package/src/design_system/elements/avatar/utils.ts +12 -0
  15. package/src/design_system/elements/badges/avatar.tsx +132 -0
  16. package/src/design_system/elements/badges/badge-groups.tsx +176 -0
  17. package/src/design_system/elements/badges/badge-types.ts +266 -0
  18. package/src/design_system/elements/badges/badges.tsx +430 -0
  19. package/src/design_system/elements/breadcrumb/Breadcrumb.tsx +33 -0
  20. package/src/design_system/elements/button-group/button-group.tsx +106 -0
  21. package/src/design_system/elements/buttons/app-store-buttons-outline.tsx +378 -0
  22. package/src/design_system/elements/buttons/app-store-buttons.tsx +567 -0
  23. package/src/design_system/elements/buttons/button-utility.tsx +116 -0
  24. package/src/design_system/elements/buttons/button.aman.tsx +174 -0
  25. package/src/design_system/elements/buttons/button.tsx +271 -0
  26. package/src/design_system/elements/buttons/close-button.tsx +42 -0
  27. package/src/design_system/elements/buttons/round-button.tsx +29 -0
  28. package/src/design_system/elements/buttons/social-button.tsx +148 -0
  29. package/src/design_system/elements/buttons/social-logos.tsx +115 -0
  30. package/src/design_system/elements/carousel/carousel-base.tsx +308 -0
  31. package/src/design_system/elements/carousel/carousel.tsx +308 -0
  32. package/src/design_system/elements/checkbox/checkbox.tsx +120 -0
  33. package/src/design_system/elements/date-picker/calendar.tsx +101 -0
  34. package/src/design_system/elements/date-picker/cell.tsx +106 -0
  35. package/src/design_system/elements/date-picker/date-input.tsx +32 -0
  36. package/src/design_system/elements/date-picker/date-picker.tsx +86 -0
  37. package/src/design_system/elements/date-picker/date-range-picker.tsx +163 -0
  38. package/src/design_system/elements/date-picker/range-calendar.tsx +161 -0
  39. package/src/design_system/elements/date-picker/range-preset.tsx +28 -0
  40. package/src/design_system/elements/featured-icon/featured-icon.tsx +154 -0
  41. package/src/design_system/elements/form/form.tsx +10 -0
  42. package/src/design_system/elements/form/hook-form.tsx +75 -0
  43. package/src/design_system/elements/hint-text/hint-text.tsx +33 -0
  44. package/src/design_system/elements/index.tsx +158 -0
  45. package/src/design_system/elements/input/hint-text.tsx +33 -0
  46. package/src/design_system/elements/input/input-group.tsx +133 -0
  47. package/src/design_system/elements/input/input.aman.tsx +172 -0
  48. package/src/design_system/elements/input/input.tsx +271 -0
  49. package/src/design_system/elements/input/label.tsx +50 -0
  50. package/src/design_system/elements/label/label.tsx +50 -0
  51. package/src/design_system/elements/loading-indicator/loading-indicator.tsx +123 -0
  52. package/src/design_system/elements/map/GoogleMap.tsx +286 -0
  53. package/src/design_system/elements/markdown-renderer/MarkdownRenderer.tsx +155 -0
  54. package/src/design_system/elements/modals/modal.tsx +41 -0
  55. package/src/design_system/elements/pagination/pagination-base.tsx +378 -0
  56. package/src/design_system/elements/pagination/pagination-dot.tsx +54 -0
  57. package/src/design_system/elements/pagination/pagination-line.tsx +50 -0
  58. package/src/design_system/elements/pagination/pagination.tsx +330 -0
  59. package/src/design_system/elements/photo-fallback/photo-fallback.tsx +143 -0
  60. package/src/design_system/elements/progress-indicators/progress-circles.tsx +176 -0
  61. package/src/design_system/elements/progress-indicators/progress-indicators.tsx +123 -0
  62. package/src/design_system/elements/progress-indicators/simple-circle.tsx +29 -0
  63. package/src/design_system/elements/radio-buttons/radio-buttons.tsx +129 -0
  64. package/src/design_system/elements/rating/rating-badge.tsx +144 -0
  65. package/src/design_system/elements/rating/rating-stars.tsx +77 -0
  66. package/src/design_system/elements/select/combobox.tsx +152 -0
  67. package/src/design_system/elements/select/multi-select.tsx +363 -0
  68. package/src/design_system/elements/select/popover.tsx +34 -0
  69. package/src/design_system/elements/select/select-item.tsx +97 -0
  70. package/src/design_system/elements/select/select-native.tsx +69 -0
  71. package/src/design_system/elements/select/select.aman.tsx +75 -0
  72. package/src/design_system/elements/select/select.tsx +146 -0
  73. package/src/design_system/elements/shared-assets/credit-card/credit-card.tsx +237 -0
  74. package/src/design_system/elements/shared-assets/credit-card/icons.tsx +75 -0
  75. package/src/design_system/elements/shared-assets/iphone-mockup.tsx +172 -0
  76. package/src/design_system/elements/shared-assets/section-divider.tsx +12 -0
  77. package/src/design_system/elements/slideout-menus/slideout-menu.tsx +122 -0
  78. package/src/design_system/elements/tabs/tabs.tsx +225 -0
  79. package/src/design_system/elements/tags/base-components/tag-checkbox.tsx +45 -0
  80. package/src/design_system/elements/tags/base-components/tag-close-x.tsx +34 -0
  81. package/src/design_system/elements/tags/tags.tsx +176 -0
  82. package/src/design_system/elements/textarea/textarea.aman.tsx +52 -0
  83. package/src/design_system/elements/textarea/textarea.tsx +111 -0
  84. package/src/design_system/elements/toggle/toggle.tsx +140 -0
  85. package/src/design_system/elements/tooltip/tooltip.tsx +109 -0
  86. package/src/design_system/hooks/use-breakpoint.ts +37 -0
  87. package/src/design_system/hooks/use-resize-observer.ts +68 -0
  88. package/src/design_system/logo/keystone-logo-minimal.tsx +93 -0
  89. package/src/design_system/logo/keystone-logo.tsx +22 -0
  90. package/src/design_system/sections/about-home.aman.tsx +85 -0
  91. package/src/design_system/sections/about-home.tsx +115 -0
  92. package/src/design_system/sections/blog-cards.tsx +848 -0
  93. package/src/design_system/sections/blog-gallery.aman.tsx +77 -0
  94. package/src/design_system/sections/blog-gallery.tsx +204 -0
  95. package/src/design_system/sections/blog-home.aman.tsx +84 -0
  96. package/src/design_system/sections/blog-home.tsx +153 -0
  97. package/src/design_system/sections/blog-post.aman.tsx +74 -0
  98. package/src/design_system/sections/blog-post.tsx +301 -0
  99. package/src/design_system/sections/blog-section.aman.tsx +101 -0
  100. package/src/design_system/sections/blog-section.tsx +179 -0
  101. package/src/design_system/sections/contact-home.tsx +25 -0
  102. package/src/design_system/sections/contact-section.aman.tsx +173 -0
  103. package/src/design_system/sections/contact-section.tsx +143 -0
  104. package/src/design_system/sections/faq-grid.aman.tsx +79 -0
  105. package/src/design_system/sections/faq-grid.tsx +102 -0
  106. package/src/design_system/sections/faq-home.aman.tsx +92 -0
  107. package/src/design_system/sections/faq-home.tsx +134 -0
  108. package/src/design_system/sections/feature-tab.tsx +43 -0
  109. package/src/design_system/sections/feature-text.tsx +284 -0
  110. package/src/design_system/sections/footer-home.aman.tsx +62 -0
  111. package/src/design_system/sections/footer-home.tsx +259 -0
  112. package/src/design_system/sections/generic-header-component.tsx +103 -0
  113. package/src/design_system/sections/header-navigation.aman.tsx +360 -0
  114. package/src/design_system/sections/header-navigation.tsx +334 -0
  115. package/src/design_system/sections/hero-faq.aman.tsx +38 -0
  116. package/src/design_system/sections/hero-faq.tsx +55 -0
  117. package/src/design_system/sections/hero-generic-text.aman.tsx +49 -0
  118. package/src/design_system/sections/hero-generic-text.tsx +51 -0
  119. package/src/design_system/sections/hero-home.aman.tsx +84 -0
  120. package/src/design_system/sections/hero-home.tsx +246 -0
  121. package/src/design_system/sections/hero-location-detail.aman.tsx +33 -0
  122. package/src/design_system/sections/hero-location-detail.tsx +72 -0
  123. package/src/design_system/sections/hero-service-detail.aman.tsx +53 -0
  124. package/src/design_system/sections/hero-service-detail.tsx +51 -0
  125. package/src/design_system/sections/hero-social-media.aman.tsx +42 -0
  126. package/src/design_system/sections/hero-social-media.tsx +35 -0
  127. package/src/design_system/sections/hero-testimonials.aman.tsx +38 -0
  128. package/src/design_system/sections/hero-testimonials.tsx +55 -0
  129. package/src/design_system/sections/home-hero-component.tsx +228 -0
  130. package/src/design_system/sections/index.tsx +131 -0
  131. package/src/design_system/sections/job-gallery.aman.tsx +91 -0
  132. package/src/design_system/sections/job-gallery.tsx +183 -0
  133. package/src/design_system/sections/location-details-section.aman.tsx +179 -0
  134. package/src/design_system/sections/location-details-section.tsx +196 -0
  135. package/src/design_system/sections/location-grid.aman.tsx +76 -0
  136. package/src/design_system/sections/location-grid.tsx +123 -0
  137. package/src/design_system/sections/services-grid.aman.tsx +85 -0
  138. package/src/design_system/sections/services-grid.tsx +104 -0
  139. package/src/design_system/sections/services-home.aman.tsx +78 -0
  140. package/src/design_system/sections/services-home.tsx +131 -0
  141. package/src/design_system/sections/social-media-grid.aman.tsx +132 -0
  142. package/src/design_system/sections/social-media-grid.tsx +189 -0
  143. package/src/design_system/sections/statistics-section.aman.tsx +79 -0
  144. package/src/design_system/sections/statistics-section.tsx +97 -0
  145. package/src/design_system/sections/team-grid.aman.tsx +85 -0
  146. package/src/design_system/sections/team-grid.tsx +88 -0
  147. package/src/design_system/sections/testimonials-home.aman.tsx +113 -0
  148. package/src/design_system/sections/testimonials-home.tsx +90 -0
  149. package/src/design_system/sections/values-section.aman.tsx +73 -0
  150. package/src/design_system/sections/values-section.tsx +128 -0
  151. package/src/design_system/utils/icon-mapping.tsx +28 -0
  152. package/src/index.ts +7 -0
  153. package/src/lib/component-registry.ts +53 -0
  154. package/src/lib/hooks/index.ts +8 -0
  155. package/src/lib/hooks/use-breakpoint.ts +37 -0
  156. package/src/lib/hooks/use-clipboard.ts +79 -0
  157. package/src/lib/hooks/use-resize-observer.ts +68 -0
  158. package/src/lib/server-api.ts +115 -0
  159. package/src/styles/style-overrides.aman.css +101 -0
  160. package/src/styles/theme.css +224 -0
  161. package/src/styles/typography.css +430 -0
  162. package/src/themes/index.ts +23 -0
  163. package/src/types/api/blog-post.ts +53 -0
  164. package/src/types/api/company-information.ts +44 -0
  165. package/src/types/api/contact.ts +63 -0
  166. package/src/types/api/faq.ts +37 -0
  167. package/src/types/api/job-posting.ts +34 -0
  168. package/src/types/api/location.ts +36 -0
  169. package/src/types/api/photos.ts +28 -0
  170. package/src/types/api/service.ts +37 -0
  171. package/src/types/api/social-post.ts +28 -0
  172. package/src/types/api/team-member.ts +29 -0
  173. package/src/types/api/testimonial.ts +29 -0
  174. package/src/types/api/website-photos.ts +22 -0
  175. package/src/types/config.ts +21 -0
  176. package/src/types/index.ts +21 -0
  177. package/src/utils/countries.tsx +1351 -0
  178. package/src/utils/cx.ts +25 -0
  179. package/src/utils/gradient-placeholder.ts +59 -0
  180. package/src/utils/is-react-component.ts +33 -0
  181. package/src/utils/markdown-toc.ts +54 -0
  182. package/src/utils/photo-helpers.ts +94 -0
@@ -0,0 +1,259 @@
1
+ import Link from "next/link";
2
+ import { getLogoUrl } from '../../utils/photo-helpers';
3
+ import type { CompanyInformation } from '../../types/api/company-information';
4
+ import type { WebsitePhotos } from '../../types/api/website-photos';
5
+ import type { NavItem, SiteConfig } from '../../types/config';
6
+
7
+ interface FooterHomeProps {
8
+ config: SiteConfig;
9
+ companyInformation?: CompanyInformation | null;
10
+ websitePhotos?: WebsitePhotos | null;
11
+ }
12
+
13
+ const FacebookIcon = () => (
14
+ <svg className="w-5 h-5 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
15
+ <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
16
+ </svg>
17
+ );
18
+
19
+ const InstagramIcon = () => (
20
+ <svg className="w-5 h-5 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
21
+ <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
22
+ </svg>
23
+ );
24
+
25
+ const YouTubeIcon = () => (
26
+ <svg className="w-5 h-5 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
27
+ <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
28
+ </svg>
29
+ );
30
+
31
+ const GoogleIcon = () => (
32
+ <svg className="w-5 h-5 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
33
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
34
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
35
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
36
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
37
+ </svg>
38
+ );
39
+
40
+ const getSocialIcon = (platform: string) => {
41
+ switch (platform.toLowerCase()) {
42
+ case 'facebook':
43
+ return <FacebookIcon />;
44
+ case 'instagram':
45
+ return <InstagramIcon />;
46
+ case 'youtube':
47
+ return <YouTubeIcon />;
48
+ case 'google':
49
+ case 'google_my_business':
50
+ return <GoogleIcon />;
51
+ default:
52
+ return null;
53
+ }
54
+ };
55
+
56
+ const COLUMN_TITLES = ['Home', 'Services', 'Locations', 'About Us'];
57
+
58
+ export const FooterHome = ({
59
+ config,
60
+ companyInformation,
61
+ websitePhotos,
62
+ }: FooterHomeProps) => {
63
+ const currentYear = new Date().getFullYear();
64
+
65
+ const apiCompanyInfo = companyInformation as any;
66
+
67
+ // Get footer navigation from config
68
+ const footerColumns = config?.navigation?.footer || [];
69
+
70
+ // Build logo from props data
71
+ const logoImage = getLogoUrl(websitePhotos);
72
+ const companyName = apiCompanyInfo?.company_name || '';
73
+ const logoText = companyName?.charAt(0)?.toUpperCase() || '';
74
+ const logo = (logoText || logoImage) ? {
75
+ text: logoText,
76
+ href: '/',
77
+ image: logoImage,
78
+ } : undefined;
79
+
80
+ // Build about text
81
+ const aboutText = apiCompanyInfo?.about_text_markdown || apiCompanyInfo?.description;
82
+
83
+ // Build contact info
84
+ const address = [
85
+ apiCompanyInfo?.primary_address_line_1,
86
+ apiCompanyInfo?.primary_address_line_2,
87
+ apiCompanyInfo?.primary_city,
88
+ apiCompanyInfo?.primary_state,
89
+ apiCompanyInfo?.primary_zip_code
90
+ ].filter(Boolean).join(', ') || undefined;
91
+
92
+ const contactInfo = (address || apiCompanyInfo?.primary_phone || apiCompanyInfo?.primary_email) ? {
93
+ address,
94
+ phone: apiCompanyInfo?.primary_phone,
95
+ email: apiCompanyInfo?.primary_email,
96
+ } : undefined;
97
+
98
+ // Build social links
99
+ const socialLinksArray = [];
100
+ if (apiCompanyInfo?.facebook_url) socialLinksArray.push({ platform: 'facebook', url: apiCompanyInfo.facebook_url });
101
+ if (apiCompanyInfo?.twitter_url) socialLinksArray.push({ platform: 'twitter', url: apiCompanyInfo.twitter_url });
102
+ if (apiCompanyInfo?.linkedin_url) socialLinksArray.push({ platform: 'linkedin', url: apiCompanyInfo.linkedin_url });
103
+ if (apiCompanyInfo?.instagram_url) socialLinksArray.push({ platform: 'instagram', url: apiCompanyInfo.instagram_url });
104
+ if (apiCompanyInfo?.youtube_url) socialLinksArray.push({ platform: 'youtube', url: apiCompanyInfo.youtube_url });
105
+ if (apiCompanyInfo?.pinterest_url) socialLinksArray.push({ platform: 'pinterest', url: apiCompanyInfo.pinterest_url });
106
+ if (apiCompanyInfo?.tiktok_url) socialLinksArray.push({ platform: 'tiktok', url: apiCompanyInfo.tiktok_url });
107
+ if (apiCompanyInfo?.google_my_business_url) socialLinksArray.push({ platform: 'google_my_business', url: apiCompanyInfo.google_my_business_url });
108
+ if (apiCompanyInfo?.yelp_url) socialLinksArray.push({ platform: 'yelp', url: apiCompanyInfo.yelp_url });
109
+ if (apiCompanyInfo?.tripadvisor_url) socialLinksArray.push({ platform: 'tripadvisor', url: apiCompanyInfo.tripadvisor_url });
110
+ const socialLinks = socialLinksArray.length > 0 ? socialLinksArray : undefined;
111
+
112
+ // Build legal links
113
+ const legalLinks = [
114
+ { label: 'Privacy Policy', href: '/privacy-policy/' },
115
+ { label: 'Terms of Service', href: '/terms-of-service/' },
116
+ { label: 'Cookie Policy', href: '/cookie-policy/' },
117
+ { label: 'Accessibility', href: '/accessibility/' },
118
+ { label: 'Sitemap', href: '/sitemap.xml' },
119
+ ];
120
+
121
+ // Build copyright text
122
+ const copyrightText = `© ${currentYear} ${apiCompanyInfo?.company_name || 'Company Name'}. All rights reserved.`;
123
+
124
+ return (
125
+ <footer className="bg-gray-900 text-white py-12">
126
+ <div className="container mx-auto px-4 md:px-8">
127
+ <div className="grid lg:grid-cols-6 gap-8">
128
+ {/* Company Info */}
129
+ <div className="lg:col-span-2">
130
+ <div className="flex items-center space-x-2 mb-6">
131
+ <Link href={logo?.href || '/'} className="flex items-center space-x-2">
132
+ {logo?.image ? (
133
+ <img
134
+ src={logo.image}
135
+ alt={companyName || 'Logo'}
136
+ className="h-10 w-10 object-contain"
137
+ />
138
+ ) : (
139
+ <div className="h-10 w-10 bg-blue-600 rounded-lg flex items-center justify-center">
140
+ <span className="text-white font-bold text-lg">
141
+ {logoText}
142
+ </span>
143
+ </div>
144
+ )}
145
+ <span className="text-2xl font-bold text-white">{companyName}</span>
146
+ </Link>
147
+ </div>
148
+
149
+ {aboutText && (
150
+ <p className="text-gray-300 mb-6 max-w-md">
151
+ {aboutText}
152
+ </p>
153
+ )}
154
+
155
+ {/* Contact Info */}
156
+ {contactInfo && (
157
+ <div className="space-y-3 mb-6">
158
+ {contactInfo.address && (
159
+ <div className="flex items-center space-x-3">
160
+ <svg className="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
161
+ <path fillRule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clipRule="evenodd" />
162
+ </svg>
163
+ <span className="text-gray-300">{contactInfo.address}</span>
164
+ </div>
165
+ )}
166
+
167
+ {contactInfo.phone && (
168
+ <div className="flex items-center space-x-3">
169
+ <svg className="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
170
+ <path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
171
+ </svg>
172
+ <a href={`tel:${contactInfo.phone}`} className="text-gray-300 hover:text-white transition-colors">
173
+ {contactInfo.phone}
174
+ </a>
175
+ </div>
176
+ )}
177
+
178
+ {contactInfo.email && (
179
+ <div className="flex items-center space-x-3">
180
+ <svg className="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
181
+ <path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
182
+ <path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
183
+ </svg>
184
+ <a href={`mailto:${contactInfo.email}`} className="text-gray-300 hover:text-white transition-colors">
185
+ {contactInfo.email}
186
+ </a>
187
+ </div>
188
+ )}
189
+ </div>
190
+ )}
191
+
192
+ {/* Social Links */}
193
+ {socialLinks && socialLinks.length > 0 && (
194
+ <div className="flex space-x-4">
195
+ {socialLinks.map((social, index) => (
196
+ <a
197
+ key={index}
198
+ href={social.url}
199
+ className="w-10 h-10 bg-gray-800 rounded-lg flex items-center justify-center text-gray-300 hover:bg-blue-600 hover:text-white transition-colors shrink-0"
200
+ aria-label={social.platform}
201
+ target="_blank"
202
+ rel="noopener noreferrer"
203
+ >
204
+ <span className="flex items-center justify-center">
205
+ {getSocialIcon(social.platform)}
206
+ </span>
207
+ </a>
208
+ ))}
209
+ </div>
210
+ )}
211
+ </div>
212
+
213
+ {/* Navigation Columns */}
214
+ {footerColumns.map((column, colIndex) => (
215
+ <div key={colIndex}>
216
+ <h3 className="text-white font-semibold mb-4">
217
+ {COLUMN_TITLES[colIndex] || `Column ${colIndex + 1}`}
218
+ </h3>
219
+ <ul className="space-y-2">
220
+ {column.map((link, linkIndex) => (
221
+ <li key={linkIndex}>
222
+ <Link
223
+ href={link.href}
224
+ className="text-gray-300 hover:text-white transition-colors"
225
+ >
226
+ {link.label}
227
+ </Link>
228
+ </li>
229
+ ))}
230
+ </ul>
231
+ </div>
232
+ ))}
233
+ </div>
234
+
235
+ {/* Bottom Bar */}
236
+ <div className="border-t border-gray-800 py-8 mt-8">
237
+ <div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
238
+ <div className="text-gray-400 text-sm">
239
+ {copyrightText}
240
+ </div>
241
+ {legalLinks && legalLinks.length > 0 && (
242
+ <div className="flex space-x-6 text-sm">
243
+ {legalLinks.map((link, index) => (
244
+ <Link
245
+ key={index}
246
+ href={link.href}
247
+ className="text-gray-400 hover:text-white transition-colors"
248
+ >
249
+ {link.label}
250
+ </Link>
251
+ ))}
252
+ </div>
253
+ )}
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </footer>
258
+ );
259
+ };
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ import { type FC, type ReactNode, isValidElement } from "react";
4
+ import Link from "next/link";
5
+ import { cx } from '../../utils/cx';
6
+ import { isReactComponent } from '../../utils/is-react-component';
7
+
8
+ interface GenericHeaderComponentProps {
9
+ items: Array<{
10
+ label: string;
11
+ href: string;
12
+ icon?: FC<{ className?: string }>;
13
+ subtitle?: string;
14
+ }>;
15
+ }
16
+
17
+ // Nav Menu Item Link component (consolidated)
18
+ interface NavMenuItemLinkProps {
19
+ href: string;
20
+ icon?: FC<{ className?: string }> | ReactNode;
21
+ iconClassName?: string;
22
+ className?: string;
23
+ title: string;
24
+ subtitle?: string;
25
+ badge?: ReactNode;
26
+ actionsContent?: ReactNode;
27
+ onClick?: (e: React.MouseEvent) => void;
28
+ }
29
+
30
+ const NavMenuItemLink = ({ href, icon: Icon, iconClassName, title, subtitle, badge, actionsContent, className, onClick }: NavMenuItemLinkProps) => {
31
+ const content = (
32
+ <>
33
+ {isValidElement(Icon) && Icon}
34
+ {isReactComponent(Icon) && <Icon className={cx("mt-0.5 size-4 shrink-0 stroke-[2.3px] text-fg-brand-primary", iconClassName)} />}
35
+
36
+ <div className="flex min-w-0 flex-1 flex-col gap-3">
37
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
38
+ <div className="flex items-center gap-2">
39
+ <span className="text-md font-semibold text-primary">{title}</span>
40
+ {badge}
41
+ </div>
42
+
43
+ {subtitle && <span className="line-clamp-2 min-w-0 text-sm text-tertiary">{subtitle}</span>}
44
+ </div>
45
+
46
+ {actionsContent}
47
+ </div>
48
+ </>
49
+ );
50
+
51
+ if (onClick) {
52
+ return (
53
+ <button
54
+ onClick={onClick}
55
+ className={cx(
56
+ "inline-flex w-full gap-3 px-4 py-3 outline-focus-ring transition duration-100 ease-linear hover:bg-primary_hover focus-visible:outline-2 sm:p-3 md:rounded-lg text-left",
57
+ className,
58
+ )}
59
+ >
60
+ {content}
61
+ </button>
62
+ );
63
+ }
64
+
65
+ return (
66
+ <Link
67
+ href={href}
68
+ className={cx(
69
+ "inline-flex w-full gap-3 px-4 py-3 outline-focus-ring transition duration-100 ease-linear hover:bg-primary_hover focus-visible:outline-2 sm:p-3 md:rounded-lg",
70
+ className,
71
+ )}
72
+ >
73
+ {content}
74
+ </Link>
75
+ );
76
+ };
77
+
78
+ /**
79
+ * Generic header dropdown menu component for navigation
80
+ * Based on the UntitledUI dropdown-menu-simple pattern from the design system
81
+ * Used in the site template header for dropdown navigation menus
82
+ */
83
+ export const GenericHeaderComponent = ({ items }: GenericHeaderComponentProps) => {
84
+ return (
85
+ <div className="px-3 pb-2 md:max-w-84 md:p-0" style={{ fontFamily: 'var(--font-body, var(--font-inter, "Inter"), -apple-system, "Segoe UI", Roboto, Arial, sans-serif)' }}>
86
+ <nav className="overflow-hidden rounded-2xl bg-primary py-2 shadow-xs ring-1 ring-secondary_alt md:p-2 md:shadow-lg">
87
+ <ul className="flex flex-col gap-0.5">
88
+ {items.map((item: any) => (
89
+ <li key={item.label || item.href}>
90
+ <NavMenuItemLink
91
+ icon={item.icon}
92
+ title={item.label}
93
+ subtitle={item.subtitle}
94
+ href={item.href}
95
+ />
96
+ </li>
97
+ ))}
98
+ </ul>
99
+ </nav>
100
+ </div>
101
+ );
102
+ };
103
+