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.
- package/README.md +179 -0
- package/package.json +59 -0
- package/src/contexts/ThemeContext.tsx +34 -0
- package/src/contexts/index.ts +1 -0
- package/src/design_system/elements/IconComponent.tsx +98 -0
- package/src/design_system/elements/avatar/avatar-label-group.tsx +30 -0
- package/src/design_system/elements/avatar/avatar-profile-photo.tsx +125 -0
- package/src/design_system/elements/avatar/avatar.tsx +131 -0
- package/src/design_system/elements/avatar/base-components/avatar-add-button.tsx +34 -0
- package/src/design_system/elements/avatar/base-components/avatar-company-icon.tsx +26 -0
- package/src/design_system/elements/avatar/base-components/avatar-online-indicator.tsx +31 -0
- package/src/design_system/elements/avatar/base-components/index.tsx +4 -0
- package/src/design_system/elements/avatar/base-components/verified-tick.tsx +34 -0
- package/src/design_system/elements/avatar/utils.ts +12 -0
- package/src/design_system/elements/badges/avatar.tsx +132 -0
- package/src/design_system/elements/badges/badge-groups.tsx +176 -0
- package/src/design_system/elements/badges/badge-types.ts +266 -0
- package/src/design_system/elements/badges/badges.tsx +430 -0
- package/src/design_system/elements/breadcrumb/Breadcrumb.tsx +33 -0
- package/src/design_system/elements/button-group/button-group.tsx +106 -0
- package/src/design_system/elements/buttons/app-store-buttons-outline.tsx +378 -0
- package/src/design_system/elements/buttons/app-store-buttons.tsx +567 -0
- package/src/design_system/elements/buttons/button-utility.tsx +116 -0
- package/src/design_system/elements/buttons/button.aman.tsx +174 -0
- package/src/design_system/elements/buttons/button.tsx +271 -0
- package/src/design_system/elements/buttons/close-button.tsx +42 -0
- package/src/design_system/elements/buttons/round-button.tsx +29 -0
- package/src/design_system/elements/buttons/social-button.tsx +148 -0
- package/src/design_system/elements/buttons/social-logos.tsx +115 -0
- package/src/design_system/elements/carousel/carousel-base.tsx +308 -0
- package/src/design_system/elements/carousel/carousel.tsx +308 -0
- package/src/design_system/elements/checkbox/checkbox.tsx +120 -0
- package/src/design_system/elements/date-picker/calendar.tsx +101 -0
- package/src/design_system/elements/date-picker/cell.tsx +106 -0
- package/src/design_system/elements/date-picker/date-input.tsx +32 -0
- package/src/design_system/elements/date-picker/date-picker.tsx +86 -0
- package/src/design_system/elements/date-picker/date-range-picker.tsx +163 -0
- package/src/design_system/elements/date-picker/range-calendar.tsx +161 -0
- package/src/design_system/elements/date-picker/range-preset.tsx +28 -0
- package/src/design_system/elements/featured-icon/featured-icon.tsx +154 -0
- package/src/design_system/elements/form/form.tsx +10 -0
- package/src/design_system/elements/form/hook-form.tsx +75 -0
- package/src/design_system/elements/hint-text/hint-text.tsx +33 -0
- package/src/design_system/elements/index.tsx +158 -0
- package/src/design_system/elements/input/hint-text.tsx +33 -0
- package/src/design_system/elements/input/input-group.tsx +133 -0
- package/src/design_system/elements/input/input.aman.tsx +172 -0
- package/src/design_system/elements/input/input.tsx +271 -0
- package/src/design_system/elements/input/label.tsx +50 -0
- package/src/design_system/elements/label/label.tsx +50 -0
- package/src/design_system/elements/loading-indicator/loading-indicator.tsx +123 -0
- package/src/design_system/elements/map/GoogleMap.tsx +286 -0
- package/src/design_system/elements/markdown-renderer/MarkdownRenderer.tsx +155 -0
- package/src/design_system/elements/modals/modal.tsx +41 -0
- package/src/design_system/elements/pagination/pagination-base.tsx +378 -0
- package/src/design_system/elements/pagination/pagination-dot.tsx +54 -0
- package/src/design_system/elements/pagination/pagination-line.tsx +50 -0
- package/src/design_system/elements/pagination/pagination.tsx +330 -0
- package/src/design_system/elements/photo-fallback/photo-fallback.tsx +143 -0
- package/src/design_system/elements/progress-indicators/progress-circles.tsx +176 -0
- package/src/design_system/elements/progress-indicators/progress-indicators.tsx +123 -0
- package/src/design_system/elements/progress-indicators/simple-circle.tsx +29 -0
- package/src/design_system/elements/radio-buttons/radio-buttons.tsx +129 -0
- package/src/design_system/elements/rating/rating-badge.tsx +144 -0
- package/src/design_system/elements/rating/rating-stars.tsx +77 -0
- package/src/design_system/elements/select/combobox.tsx +152 -0
- package/src/design_system/elements/select/multi-select.tsx +363 -0
- package/src/design_system/elements/select/popover.tsx +34 -0
- package/src/design_system/elements/select/select-item.tsx +97 -0
- package/src/design_system/elements/select/select-native.tsx +69 -0
- package/src/design_system/elements/select/select.aman.tsx +75 -0
- package/src/design_system/elements/select/select.tsx +146 -0
- package/src/design_system/elements/shared-assets/credit-card/credit-card.tsx +237 -0
- package/src/design_system/elements/shared-assets/credit-card/icons.tsx +75 -0
- package/src/design_system/elements/shared-assets/iphone-mockup.tsx +172 -0
- package/src/design_system/elements/shared-assets/section-divider.tsx +12 -0
- package/src/design_system/elements/slideout-menus/slideout-menu.tsx +122 -0
- package/src/design_system/elements/tabs/tabs.tsx +225 -0
- package/src/design_system/elements/tags/base-components/tag-checkbox.tsx +45 -0
- package/src/design_system/elements/tags/base-components/tag-close-x.tsx +34 -0
- package/src/design_system/elements/tags/tags.tsx +176 -0
- package/src/design_system/elements/textarea/textarea.aman.tsx +52 -0
- package/src/design_system/elements/textarea/textarea.tsx +111 -0
- package/src/design_system/elements/toggle/toggle.tsx +140 -0
- package/src/design_system/elements/tooltip/tooltip.tsx +109 -0
- package/src/design_system/hooks/use-breakpoint.ts +37 -0
- package/src/design_system/hooks/use-resize-observer.ts +68 -0
- package/src/design_system/logo/keystone-logo-minimal.tsx +93 -0
- package/src/design_system/logo/keystone-logo.tsx +22 -0
- package/src/design_system/sections/about-home.aman.tsx +85 -0
- package/src/design_system/sections/about-home.tsx +115 -0
- package/src/design_system/sections/blog-cards.tsx +848 -0
- package/src/design_system/sections/blog-gallery.aman.tsx +77 -0
- package/src/design_system/sections/blog-gallery.tsx +204 -0
- package/src/design_system/sections/blog-home.aman.tsx +84 -0
- package/src/design_system/sections/blog-home.tsx +153 -0
- package/src/design_system/sections/blog-post.aman.tsx +74 -0
- package/src/design_system/sections/blog-post.tsx +301 -0
- package/src/design_system/sections/blog-section.aman.tsx +101 -0
- package/src/design_system/sections/blog-section.tsx +179 -0
- package/src/design_system/sections/contact-home.tsx +25 -0
- package/src/design_system/sections/contact-section.aman.tsx +173 -0
- package/src/design_system/sections/contact-section.tsx +143 -0
- package/src/design_system/sections/faq-grid.aman.tsx +79 -0
- package/src/design_system/sections/faq-grid.tsx +102 -0
- package/src/design_system/sections/faq-home.aman.tsx +92 -0
- package/src/design_system/sections/faq-home.tsx +134 -0
- package/src/design_system/sections/feature-tab.tsx +43 -0
- package/src/design_system/sections/feature-text.tsx +284 -0
- package/src/design_system/sections/footer-home.aman.tsx +62 -0
- package/src/design_system/sections/footer-home.tsx +259 -0
- package/src/design_system/sections/generic-header-component.tsx +103 -0
- package/src/design_system/sections/header-navigation.aman.tsx +360 -0
- package/src/design_system/sections/header-navigation.tsx +334 -0
- package/src/design_system/sections/hero-faq.aman.tsx +38 -0
- package/src/design_system/sections/hero-faq.tsx +55 -0
- package/src/design_system/sections/hero-generic-text.aman.tsx +49 -0
- package/src/design_system/sections/hero-generic-text.tsx +51 -0
- package/src/design_system/sections/hero-home.aman.tsx +84 -0
- package/src/design_system/sections/hero-home.tsx +246 -0
- package/src/design_system/sections/hero-location-detail.aman.tsx +33 -0
- package/src/design_system/sections/hero-location-detail.tsx +72 -0
- package/src/design_system/sections/hero-service-detail.aman.tsx +53 -0
- package/src/design_system/sections/hero-service-detail.tsx +51 -0
- package/src/design_system/sections/hero-social-media.aman.tsx +42 -0
- package/src/design_system/sections/hero-social-media.tsx +35 -0
- package/src/design_system/sections/hero-testimonials.aman.tsx +38 -0
- package/src/design_system/sections/hero-testimonials.tsx +55 -0
- package/src/design_system/sections/home-hero-component.tsx +228 -0
- package/src/design_system/sections/index.tsx +131 -0
- package/src/design_system/sections/job-gallery.aman.tsx +91 -0
- package/src/design_system/sections/job-gallery.tsx +183 -0
- package/src/design_system/sections/location-details-section.aman.tsx +179 -0
- package/src/design_system/sections/location-details-section.tsx +196 -0
- package/src/design_system/sections/location-grid.aman.tsx +76 -0
- package/src/design_system/sections/location-grid.tsx +123 -0
- package/src/design_system/sections/services-grid.aman.tsx +85 -0
- package/src/design_system/sections/services-grid.tsx +104 -0
- package/src/design_system/sections/services-home.aman.tsx +78 -0
- package/src/design_system/sections/services-home.tsx +131 -0
- package/src/design_system/sections/social-media-grid.aman.tsx +132 -0
- package/src/design_system/sections/social-media-grid.tsx +189 -0
- package/src/design_system/sections/statistics-section.aman.tsx +79 -0
- package/src/design_system/sections/statistics-section.tsx +97 -0
- package/src/design_system/sections/team-grid.aman.tsx +85 -0
- package/src/design_system/sections/team-grid.tsx +88 -0
- package/src/design_system/sections/testimonials-home.aman.tsx +113 -0
- package/src/design_system/sections/testimonials-home.tsx +90 -0
- package/src/design_system/sections/values-section.aman.tsx +73 -0
- package/src/design_system/sections/values-section.tsx +128 -0
- package/src/design_system/utils/icon-mapping.tsx +28 -0
- package/src/index.ts +7 -0
- package/src/lib/component-registry.ts +53 -0
- package/src/lib/hooks/index.ts +8 -0
- package/src/lib/hooks/use-breakpoint.ts +37 -0
- package/src/lib/hooks/use-clipboard.ts +79 -0
- package/src/lib/hooks/use-resize-observer.ts +68 -0
- package/src/lib/server-api.ts +115 -0
- package/src/styles/style-overrides.aman.css +101 -0
- package/src/styles/theme.css +224 -0
- package/src/styles/typography.css +430 -0
- package/src/themes/index.ts +23 -0
- package/src/types/api/blog-post.ts +53 -0
- package/src/types/api/company-information.ts +44 -0
- package/src/types/api/contact.ts +63 -0
- package/src/types/api/faq.ts +37 -0
- package/src/types/api/job-posting.ts +34 -0
- package/src/types/api/location.ts +36 -0
- package/src/types/api/photos.ts +28 -0
- package/src/types/api/service.ts +37 -0
- package/src/types/api/social-post.ts +28 -0
- package/src/types/api/team-member.ts +29 -0
- package/src/types/api/testimonial.ts +29 -0
- package/src/types/api/website-photos.ts +22 -0
- package/src/types/config.ts +21 -0
- package/src/types/index.ts +21 -0
- package/src/utils/countries.tsx +1351 -0
- package/src/utils/cx.ts +25 -0
- package/src/utils/gradient-placeholder.ts +59 -0
- package/src/utils/is-react-component.ts +33 -0
- package/src/utils/markdown-toc.ts +54 -0
- package/src/utils/photo-helpers.ts +94 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Breadcrumb } from '../elements';
|
|
4
|
+
import type { Service } from '../../types/api/service';
|
|
5
|
+
|
|
6
|
+
interface HeroServiceDetailProps {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
backgroundImage?: string;
|
|
10
|
+
breadcrumbs?: Array<{ label: string; href: string }>;
|
|
11
|
+
config?: {
|
|
12
|
+
pages?: any[];
|
|
13
|
+
};
|
|
14
|
+
service?: Service | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ServiceDetailHero = ({
|
|
18
|
+
title: titleProp,
|
|
19
|
+
description: descriptionProp,
|
|
20
|
+
breadcrumbs = [],
|
|
21
|
+
config,
|
|
22
|
+
service,
|
|
23
|
+
}: HeroServiceDetailProps) => {
|
|
24
|
+
const title = service?.name || titleProp || '';
|
|
25
|
+
const description = service?.summary || descriptionProp || '';
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<section className="relative">
|
|
29
|
+
<div className="mx-auto max-w-container px-4 md:px-8">
|
|
30
|
+
{breadcrumbs && breadcrumbs.length > 0 && (
|
|
31
|
+
<div className="mb-8">
|
|
32
|
+
<Breadcrumb items={breadcrumbs} />
|
|
33
|
+
</div>
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
<div className="text-center max-w-4xl mx-auto">
|
|
37
|
+
<h1 className="font-display text-5xl font-normal leading-tight text-fg-primary md:text-6xl">
|
|
38
|
+
{title}
|
|
39
|
+
</h1>
|
|
40
|
+
|
|
41
|
+
{description && (
|
|
42
|
+
<p className="mt-6 font-display text-lg leading-relaxed text-tertiary md:text-xl">
|
|
43
|
+
{description}
|
|
44
|
+
</p>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</section>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
import { registerThemeVariant } from '../../lib/component-registry';
|
|
53
|
+
registerThemeVariant('hero-service-detail', 'aman', ServiceDetailHero);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronLeft } from "@untitledui/icons";
|
|
4
|
+
import { Button } from '../elements';
|
|
5
|
+
import type { Service } from '../../types/api/service';
|
|
6
|
+
|
|
7
|
+
interface ServiceDetailHeroProps {
|
|
8
|
+
config: {
|
|
9
|
+
pages?: any[];
|
|
10
|
+
};
|
|
11
|
+
service?: Service | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ServiceDetailHero = ({
|
|
15
|
+
config,
|
|
16
|
+
service,
|
|
17
|
+
}: ServiceDetailHeroProps) => {
|
|
18
|
+
if (!service) return null;
|
|
19
|
+
|
|
20
|
+
// Build breadcrumb from service data
|
|
21
|
+
const breadcrumb = {
|
|
22
|
+
backHref: "/services",
|
|
23
|
+
backLabel: "Services",
|
|
24
|
+
currentLabel: service.name || "Service",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<section className="bg-primary py-4 md:py-6">
|
|
29
|
+
<div className="mx-auto max-w-container px-4 md:px-8">
|
|
30
|
+
{/* Breadcrumb - top left aligned */}
|
|
31
|
+
<div className="mb-8">
|
|
32
|
+
<nav aria-label="Breadcrumb">
|
|
33
|
+
<div className="flex items-center gap-2">
|
|
34
|
+
<Button
|
|
35
|
+
color="link-gray"
|
|
36
|
+
size="md"
|
|
37
|
+
href={breadcrumb.backHref}
|
|
38
|
+
iconLeading={ChevronLeft}
|
|
39
|
+
className="text-tertiary hover:text-primary"
|
|
40
|
+
>
|
|
41
|
+
{breadcrumb.backLabel}
|
|
42
|
+
</Button>
|
|
43
|
+
<span className="text-tertiary">/</span>
|
|
44
|
+
<span className="text-primary font-medium">{breadcrumb.currentLabel}</span>
|
|
45
|
+
</div>
|
|
46
|
+
</nav>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface SocialMediaHeroProps {
|
|
5
|
+
config: {
|
|
6
|
+
pages?: any[];
|
|
7
|
+
};
|
|
8
|
+
pageName?: string;
|
|
9
|
+
sectionKey?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const SocialMediaHero = ({
|
|
13
|
+
config,
|
|
14
|
+
pageName = 'SocialMedia',
|
|
15
|
+
sectionKey = 'social_media_page_section_1_hero',
|
|
16
|
+
}: SocialMediaHeroProps) => {
|
|
17
|
+
// Get the page to extract nav_title
|
|
18
|
+
const page = config?.pages?.find((p: any) => p.library_reference_name === pageName);
|
|
19
|
+
const navTitle = page?.nav_title || 'Social Media';
|
|
20
|
+
|
|
21
|
+
const title = navTitle;
|
|
22
|
+
const subtitle = '';
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<section className="py-24 md:py-32">
|
|
26
|
+
<div className="mx-auto max-w-4xl px-4 text-center md:px-8">
|
|
27
|
+
<h1 className="font-display text-5xl font-normal leading-tight text-fg-primary md:text-6xl lg:text-7xl">
|
|
28
|
+
{title}
|
|
29
|
+
</h1>
|
|
30
|
+
|
|
31
|
+
{subtitle && (
|
|
32
|
+
<p className="mt-6 font-display text-lg leading-relaxed text-tertiary md:text-xl max-w-3xl mx-auto">
|
|
33
|
+
{subtitle}
|
|
34
|
+
</p>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
import { registerThemeVariant } from '../../lib/component-registry';
|
|
42
|
+
registerThemeVariant('hero-social-media', 'aman', SocialMediaHero);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface SocialMediaHeroProps {
|
|
5
|
+
// Config data - component extracts all values from it
|
|
6
|
+
config: {
|
|
7
|
+
pages?: any[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const SocialMediaHero = ({
|
|
12
|
+
config,
|
|
13
|
+
}: SocialMediaHeroProps) => {
|
|
14
|
+
// Extract values from config
|
|
15
|
+
const label = "Social Media";
|
|
16
|
+
const headline = "Follow Us";
|
|
17
|
+
const subhead = "";
|
|
18
|
+
return (
|
|
19
|
+
<section className="bg-primary py-16 md:py-24">
|
|
20
|
+
<div className="mx-auto max-w-container px-4 md:px-8">
|
|
21
|
+
<div className="mx-auto flex w-full max-w-3xl flex-col items-center text-center">
|
|
22
|
+
<span className="text-sm font-semibold text-brand-secondary md:text-md">{label}</span>
|
|
23
|
+
<h1 className="mt-3 text-display-md font-semibold text-primary md:text-display-lg">
|
|
24
|
+
{headline}
|
|
25
|
+
</h1>
|
|
26
|
+
<p className="mt-4 text-lg text-tertiary md:mt-6 md:text-xl">
|
|
27
|
+
{subhead}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</section>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface TestimonialsHeroProps {
|
|
5
|
+
config: {
|
|
6
|
+
pages?: any[];
|
|
7
|
+
};
|
|
8
|
+
pageName?: string;
|
|
9
|
+
sectionKey?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TestimonialsHero = ({
|
|
13
|
+
config,
|
|
14
|
+
pageName = 'Testimonials',
|
|
15
|
+
sectionKey = 'testimonials_page_section_1_hero',
|
|
16
|
+
}: TestimonialsHeroProps) => {
|
|
17
|
+
const title = 'Client Testimonials';
|
|
18
|
+
const subtitle = '';
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<section className="py-24 md:py-32">
|
|
22
|
+
<div className="mx-auto max-w-4xl px-4 text-center md:px-8">
|
|
23
|
+
<h1 className="font-display text-5xl font-normal leading-tight text-fg-primary md:text-6xl lg:text-7xl">
|
|
24
|
+
{title}
|
|
25
|
+
</h1>
|
|
26
|
+
|
|
27
|
+
{subtitle && (
|
|
28
|
+
<p className="mt-6 font-display text-lg leading-relaxed text-tertiary md:text-xl max-w-3xl mx-auto">
|
|
29
|
+
{subtitle}
|
|
30
|
+
</p>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
</section>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
import { registerThemeVariant } from '../../lib/component-registry';
|
|
38
|
+
registerThemeVariant('hero-testimonials', 'aman', TestimonialsHero);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { SearchLg } from '@untitledui/icons';
|
|
4
|
+
import { Input } from '../elements';
|
|
5
|
+
|
|
6
|
+
interface TestimonialsHeroProps {
|
|
7
|
+
// Config data - component extracts all values from it
|
|
8
|
+
config: {
|
|
9
|
+
pages?: any[];
|
|
10
|
+
};
|
|
11
|
+
// Optional search props
|
|
12
|
+
searchTerm?: string;
|
|
13
|
+
onSearchChange?: (value: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const TestimonialsHero = ({
|
|
17
|
+
config,
|
|
18
|
+
searchTerm,
|
|
19
|
+
onSearchChange,
|
|
20
|
+
}: TestimonialsHeroProps) => {
|
|
21
|
+
// Extract values from config
|
|
22
|
+
const label = "Testimonials";
|
|
23
|
+
const headline = "What our customers say";
|
|
24
|
+
const subhead = "";
|
|
25
|
+
return (
|
|
26
|
+
<section className="bg-primary py-16 md:py-24">
|
|
27
|
+
<div className="mx-auto max-w-container px-4 md:px-8">
|
|
28
|
+
<div className="mx-auto flex w-full max-w-3xl flex-col items-center text-center">
|
|
29
|
+
<span className="text-sm font-semibold text-brand-secondary md:text-md">{label}</span>
|
|
30
|
+
<h1 className="mt-3 text-display-md font-semibold text-primary md:text-display-lg">
|
|
31
|
+
{headline}
|
|
32
|
+
</h1>
|
|
33
|
+
<p className="mt-4 text-lg text-tertiary md:mt-6 md:text-xl">
|
|
34
|
+
{subhead}
|
|
35
|
+
</p>
|
|
36
|
+
{onSearchChange && (
|
|
37
|
+
<div className="mt-8 w-full sm:mt-12 sm:w-80">
|
|
38
|
+
<Input
|
|
39
|
+
size="md"
|
|
40
|
+
type="search"
|
|
41
|
+
placeholder="Search testimonials"
|
|
42
|
+
icon={SearchLg}
|
|
43
|
+
wrapperClassName="sm:py-0.5"
|
|
44
|
+
value={searchTerm || ''}
|
|
45
|
+
onChange={onSearchChange}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import { Button, Form, Input } from '../elements';
|
|
5
|
+
import { cx } from '../../utils/cx';
|
|
6
|
+
import IconComponent from '../elements/IconComponent';
|
|
7
|
+
import type { WebsitePhotos } from '../../types/api/website-photos';
|
|
8
|
+
|
|
9
|
+
interface HomeHeroComponentProps {
|
|
10
|
+
headline: string;
|
|
11
|
+
subhead: string;
|
|
12
|
+
email_signup?: {
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
button_text?: string;
|
|
15
|
+
privacy_policy_link?: string;
|
|
16
|
+
};
|
|
17
|
+
reviews?: {
|
|
18
|
+
rating?: number;
|
|
19
|
+
count?: number;
|
|
20
|
+
avatars?: Array<{
|
|
21
|
+
url: string;
|
|
22
|
+
alt: string;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
statistics?: Array<{
|
|
26
|
+
number: string;
|
|
27
|
+
label: string;
|
|
28
|
+
icon: string;
|
|
29
|
+
color: string;
|
|
30
|
+
}>;
|
|
31
|
+
onEmailSubmit?: (email: string) => void;
|
|
32
|
+
websitePhotos?: WebsitePhotos | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const AvatarsWithReview = ({
|
|
36
|
+
rating = 5.0,
|
|
37
|
+
count = 200,
|
|
38
|
+
avatars = [
|
|
39
|
+
{ url: "https://www.untitledui.com/images/avatars/olivia-rhye?fm=webp&q=80", alt: "Olivia Rhye" },
|
|
40
|
+
{ url: "https://www.untitledui.com/images/avatars/phoenix-baker?fm=webp&q=80", alt: "Phoenix Baker" },
|
|
41
|
+
{ url: "https://www.untitledui.com/images/avatars/lana-steiner?fm=webp&q=80", alt: "Lana Steiner" },
|
|
42
|
+
{ url: "https://www.untitledui.com/images/avatars/demi-wilkinson?fm=webp&q=80", alt: "Demi Wilkinson" },
|
|
43
|
+
{ url: "https://www.untitledui.com/images/avatars/candice-wu?fm=webp&q=80", alt: "Candice Wu" },
|
|
44
|
+
],
|
|
45
|
+
className
|
|
46
|
+
}: {
|
|
47
|
+
rating?: number;
|
|
48
|
+
count?: number;
|
|
49
|
+
avatars?: Array<{ url: string; alt: string }>;
|
|
50
|
+
className?: string;
|
|
51
|
+
}) => {
|
|
52
|
+
return (
|
|
53
|
+
<div className={cx("flex items-center gap-4", className)}>
|
|
54
|
+
<div className="inline-flex -space-x-3 overflow-hidden">
|
|
55
|
+
{avatars.map((avatar, index) => (
|
|
56
|
+
<img
|
|
57
|
+
key={index}
|
|
58
|
+
className="inline-block size-10 rounded-full ring-[1.5px] ring-bg-primary outline-1 -outline-offset-1 outline-avatar-contrast-border"
|
|
59
|
+
src={avatar.url}
|
|
60
|
+
alt={avatar.alt}
|
|
61
|
+
/>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex flex-col gap-0.5">
|
|
65
|
+
<div className="flex items-center gap-2">
|
|
66
|
+
<div className="flex items-center gap-1">
|
|
67
|
+
{Array(5)
|
|
68
|
+
.fill(null)
|
|
69
|
+
.map((_, index) => {
|
|
70
|
+
const clipId0 = `clip0_star_${index}`;
|
|
71
|
+
const clipId1 = `clip1_star_${index}`;
|
|
72
|
+
return (
|
|
73
|
+
<svg
|
|
74
|
+
key={index}
|
|
75
|
+
width={20}
|
|
76
|
+
height={20}
|
|
77
|
+
viewBox="0 0 20 20"
|
|
78
|
+
fill="none"
|
|
79
|
+
className="relative size-5 shrink-0 grow-0"
|
|
80
|
+
preserveAspectRatio="none"
|
|
81
|
+
>
|
|
82
|
+
<g clipPath={`url(#${clipId0})`}>
|
|
83
|
+
<path
|
|
84
|
+
d="M9.53834 1.60996C9.70914 1.19932 10.2909 1.19932 10.4617 1.60996L12.5278 6.57744C12.5998 6.75056 12.7626 6.86885 12.9495 6.88383L18.3123 7.31376C18.7556 7.3493 18.9354 7.90256 18.5976 8.19189L14.5117 11.6919C14.3693 11.8139 14.3071 12.0053 14.3506 12.1876L15.5989 17.4208C15.7021 17.8534 15.2315 18.1954 14.8519 17.9635L10.2606 15.1592C10.1006 15.0615 9.89938 15.0615 9.73937 15.1592L5.14806 17.9635C4.76851 18.1954 4.29788 17.8534 4.40108 17.4208L5.64939 12.1876C5.69289 12.0053 5.6307 11.8139 5.48831 11.6919L1.40241 8.19189C1.06464 7.90256 1.24441 7.3493 1.68773 7.31376L7.05054 6.88383C7.23744 6.86885 7.40024 6.75056 7.47225 6.57744L9.53834 1.60996Z"
|
|
85
|
+
className="fill-bg-tertiary"
|
|
86
|
+
/>
|
|
87
|
+
<g clipPath={`url(#${clipId1})`}>
|
|
88
|
+
<path
|
|
89
|
+
d="M9.53834 1.60996C9.70914 1.19932 10.2909 1.19932 10.4617 1.60996L12.5278 6.57744C12.5998 6.75056 12.7626 6.86885 12.9495 6.88383L18.3123 7.31376C18.7556 7.3493 18.9354 7.90256 18.5976 8.19189L14.5117 11.6919C14.3693 11.8139 14.3071 12.0053 14.3506 12.1876L15.5989 17.4208C15.7021 17.8534 15.2315 18.1954 14.8519 17.9635L10.2606 15.1592C10.1006 15.0615 9.89938 15.0615 9.73937 15.1592L5.14806 17.9635C4.76851 18.1954 4.29788 17.8534 4.40108 17.4208L5.64939 12.1876C5.69289 12.0053 5.6307 11.8139 5.48831 11.6919L1.40241 8.19189C1.06464 7.90256 1.24441 7.3493 1.68773 7.31376L7.05054 6.88383C7.23744 6.86885 7.40024 6.75056 7.47225 6.57744L9.53834 1.60996Z"
|
|
90
|
+
className="fill-warning-300"
|
|
91
|
+
/>
|
|
92
|
+
</g>
|
|
93
|
+
</g>
|
|
94
|
+
<defs>
|
|
95
|
+
<clipPath id={clipId0}>
|
|
96
|
+
<rect width={20} height={20} fill="white" />
|
|
97
|
+
</clipPath>
|
|
98
|
+
<clipPath id={clipId1}>
|
|
99
|
+
<rect width={20} height={20} fill="white" />
|
|
100
|
+
</clipPath>
|
|
101
|
+
</defs>
|
|
102
|
+
</svg>
|
|
103
|
+
);
|
|
104
|
+
})}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<p className="text-md font-medium text-tertiary">from {count}+ reviews</p>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const HomeHeroComponent = ({
|
|
114
|
+
headline,
|
|
115
|
+
subhead,
|
|
116
|
+
email_signup,
|
|
117
|
+
reviews,
|
|
118
|
+
statistics,
|
|
119
|
+
onEmailSubmit,
|
|
120
|
+
websitePhotos,
|
|
121
|
+
}: HomeHeroComponentProps) => {
|
|
122
|
+
// Get hero image from website photos prop
|
|
123
|
+
const heroImage = websitePhotos?.hero ? {
|
|
124
|
+
url: websitePhotos.hero.url,
|
|
125
|
+
alt: websitePhotos.hero.alt
|
|
126
|
+
} : null;
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<Fragment>
|
|
130
|
+
<section className="bg-primary py-16 md:pb-24">
|
|
131
|
+
<div className="mx-auto grid max-w-container grid-cols-1 items-center gap-16 px-4 md:px-8 lg:grid-cols-2 lg:gap-8">
|
|
132
|
+
<div className="flex max-w-3xl flex-col items-start lg:pr-8">
|
|
133
|
+
<h1 className="text-display-md font-semibold text-primary md:text-display-lg lg:text-display-xl">
|
|
134
|
+
{headline}
|
|
135
|
+
</h1>
|
|
136
|
+
<p className="mt-4 max-w-lg text-lg text-balance text-tertiary md:mt-6 md:text-xl">
|
|
137
|
+
{subhead}
|
|
138
|
+
</p>
|
|
139
|
+
|
|
140
|
+
{email_signup && (
|
|
141
|
+
<Form
|
|
142
|
+
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
const formData = new FormData(e.currentTarget);
|
|
145
|
+
const email = formData.get("email") as string;
|
|
146
|
+
if (onEmailSubmit) {
|
|
147
|
+
onEmailSubmit(email);
|
|
148
|
+
} else {
|
|
149
|
+
console.log("Email signup:", email);
|
|
150
|
+
}
|
|
151
|
+
}}
|
|
152
|
+
className="mt-8 flex w-full flex-col items-stretch gap-4 md:mt-12 md:max-w-120 md:flex-row md:items-start"
|
|
153
|
+
>
|
|
154
|
+
<Input
|
|
155
|
+
isRequired
|
|
156
|
+
size="md"
|
|
157
|
+
name="email"
|
|
158
|
+
type="email"
|
|
159
|
+
placeholder={email_signup.placeholder || "Enter your email"}
|
|
160
|
+
wrapperClassName="py-0.5"
|
|
161
|
+
hint={
|
|
162
|
+
<span>
|
|
163
|
+
We care about your data in our{" "}
|
|
164
|
+
<a
|
|
165
|
+
href={email_signup.privacy_policy_link || "#"}
|
|
166
|
+
className="rounded-xs underline underline-offset-3 outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
167
|
+
>
|
|
168
|
+
privacy policy
|
|
169
|
+
</a>
|
|
170
|
+
.
|
|
171
|
+
</span>
|
|
172
|
+
}
|
|
173
|
+
/>
|
|
174
|
+
<Button type="submit" color="primary" size="xl">
|
|
175
|
+
{email_signup.button_text || "Get started"}
|
|
176
|
+
</Button>
|
|
177
|
+
</Form>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
<AvatarsWithReview
|
|
181
|
+
rating={reviews?.rating}
|
|
182
|
+
count={reviews?.count}
|
|
183
|
+
avatars={reviews?.avatars}
|
|
184
|
+
className="mt-8 md:mt-12"
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
{/* Statistics in 2x2 grid */}
|
|
188
|
+
{statistics && statistics.length > 0 && (
|
|
189
|
+
<dl className="mt-8 grid grid-cols-2 gap-x-6 gap-y-4 md:mt-12">
|
|
190
|
+
{statistics.slice(0, 4).map((stat, index) => {
|
|
191
|
+
const IconWrapper = ({ className }: { className?: string }) => (
|
|
192
|
+
<div className={className}>
|
|
193
|
+
<IconComponent
|
|
194
|
+
icon={stat.icon}
|
|
195
|
+
color={stat.color || 'blue'}
|
|
196
|
+
className="w-full h-full"
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div key={index} className="flex flex-col gap-2">
|
|
203
|
+
<div className="flex items-center gap-2">
|
|
204
|
+
<IconWrapper className="size-5 shrink-0" />
|
|
205
|
+
<dd className="text-lg font-semibold text-primary md:text-xl">{stat.number}</dd>
|
|
206
|
+
</div>
|
|
207
|
+
<dt className="text-sm font-medium text-tertiary md:text-md">{stat.label}</dt>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
})}
|
|
211
|
+
</dl>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{heroImage && heroImage.url && (
|
|
216
|
+
<div className="relative lg:h-full lg:min-h-160">
|
|
217
|
+
<img
|
|
218
|
+
className="inset-0 h-70 w-full object-cover md:h-110 lg:absolute lg:h-full"
|
|
219
|
+
src={heroImage.url}
|
|
220
|
+
alt={heroImage.alt}
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
</section>
|
|
226
|
+
</Fragment>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Section Exports
|
|
3
|
+
* All sections export from here with theme variant support
|
|
4
|
+
* Includes all 34 section components (excluding index.tsx and .modern variants)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { getThemedComponent } from '../../lib/component-registry';
|
|
11
|
+
import { useTheme } from '../../contexts/ThemeContext';
|
|
12
|
+
|
|
13
|
+
// Import base components (these are the defaults/classic theme)
|
|
14
|
+
import { HeroHome as BaseHeroHome } from './hero-home';
|
|
15
|
+
import { ServicesHome as BaseServicesHome } from './services-home';
|
|
16
|
+
import { AboutHome as BaseAboutHome } from './about-home';
|
|
17
|
+
import { TestimonialsHome as BaseTestimonialsHome } from './testimonials-home';
|
|
18
|
+
import { FAQHome as BaseFAQHome } from './faq-home';
|
|
19
|
+
import { BlogSection as BaseBlogSection } from './blog-section';
|
|
20
|
+
import ContactSectionBase from './contact-section';
|
|
21
|
+
import { FooterHome as BaseFooterHome } from './footer-home';
|
|
22
|
+
import { HeaderNavigation as BaseHeaderNavigation } from './header-navigation';
|
|
23
|
+
import { GenericHeaderComponent as BaseGenericHeaderComponent } from './generic-header-component';
|
|
24
|
+
import { StatisticsSection as BaseStatisticsSection } from './statistics-section';
|
|
25
|
+
import { ValuesSection as BaseValuesSection } from './values-section';
|
|
26
|
+
import { TeamGrid as BaseTeamGrid } from './team-grid';
|
|
27
|
+
import { LocationGrid as BaseLocationGrid } from './location-grid';
|
|
28
|
+
import { LocationDetailsSection as BaseLocationDetailsSection } from './location-details-section';
|
|
29
|
+
import { ServicesGrid as BaseServicesGrid } from './services-grid';
|
|
30
|
+
import { SocialMediaGrid as BaseSocialMediaGrid } from './social-media-grid';
|
|
31
|
+
import { JobGallery as BaseJobGallery } from './job-gallery';
|
|
32
|
+
import { BlogHome as BaseBlogHome } from './blog-home';
|
|
33
|
+
import { BlogGallery as BaseBlogGallery } from './blog-gallery';
|
|
34
|
+
import { BlogPostSection as BaseBlogPostSection } from './blog-post';
|
|
35
|
+
import { ContactHome as BaseContactHome } from './contact-home';
|
|
36
|
+
import { FAQGrid as BaseFAQGrid } from './faq-grid';
|
|
37
|
+
import { FAQHero as BaseFAQHero } from './hero-faq';
|
|
38
|
+
import { GenericTextHero as BaseGenericTextHero } from './hero-generic-text';
|
|
39
|
+
import { LocationDetailHero as BaseLocationDetailHero } from './hero-location-detail';
|
|
40
|
+
import { ServiceDetailHero as BaseServiceDetailHero } from './hero-service-detail';
|
|
41
|
+
import { SocialMediaHero as BaseSocialMediaHero } from './hero-social-media';
|
|
42
|
+
import { TestimonialsHero as BaseTestimonialsHero } from './hero-testimonials';
|
|
43
|
+
import { HomeHeroComponent as BaseHomeHeroComponent } from './home-hero-component';
|
|
44
|
+
|
|
45
|
+
// Import variant files to trigger their registration
|
|
46
|
+
import './hero-home.aman';
|
|
47
|
+
import './header-navigation.aman';
|
|
48
|
+
import './about-home.aman';
|
|
49
|
+
import './services-home.aman';
|
|
50
|
+
import './services-grid.aman';
|
|
51
|
+
import './testimonials-home.aman';
|
|
52
|
+
import './contact-section.aman';
|
|
53
|
+
import './team-grid.aman';
|
|
54
|
+
import './footer-home.aman';
|
|
55
|
+
import './hero-service-detail.aman';
|
|
56
|
+
import './values-section.aman';
|
|
57
|
+
import './statistics-section.aman';
|
|
58
|
+
import './location-grid.aman';
|
|
59
|
+
import './location-details-section.aman';
|
|
60
|
+
import './social-media-grid.aman';
|
|
61
|
+
import './blog-home.aman';
|
|
62
|
+
import './blog-gallery.aman';
|
|
63
|
+
import './blog-post.aman';
|
|
64
|
+
import './blog-section.aman';
|
|
65
|
+
import './faq-home.aman';
|
|
66
|
+
import './faq-grid.aman';
|
|
67
|
+
import './hero-faq.aman';
|
|
68
|
+
import './hero-generic-text.aman';
|
|
69
|
+
import './hero-location-detail.aman';
|
|
70
|
+
import './hero-social-media.aman';
|
|
71
|
+
import './hero-testimonials.aman';
|
|
72
|
+
import './job-gallery.aman';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a themed component wrapper that checks for variants
|
|
76
|
+
* Falls back to base component if no variant exists
|
|
77
|
+
*/
|
|
78
|
+
function createThemedExport(componentName: string, BaseComponent: React.ComponentType<any>) {
|
|
79
|
+
return function ThemedComponent(props: any) {
|
|
80
|
+
const { theme } = useTheme();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Try to get themed variant from registry
|
|
84
|
+
const Component = getThemedComponent(componentName, theme);
|
|
85
|
+
return React.createElement(Component, props);
|
|
86
|
+
} catch {
|
|
87
|
+
// No variant registered, use base component
|
|
88
|
+
return React.createElement(BaseComponent, props);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// All components wrapped with themed export (falls back to base if no variant)
|
|
94
|
+
export const HeroHome = createThemedExport('hero-home', BaseHeroHome);
|
|
95
|
+
export const ServicesHome = createThemedExport('services-home', BaseServicesHome);
|
|
96
|
+
export const AboutHome = createThemedExport('about-home', BaseAboutHome);
|
|
97
|
+
export const TestimonialsHome = createThemedExport('testimonials-home', BaseTestimonialsHome);
|
|
98
|
+
export const FAQHome = createThemedExport('faq-home', BaseFAQHome);
|
|
99
|
+
export const BlogSection = createThemedExport('blog-section', BaseBlogSection);
|
|
100
|
+
export const ContactSection = createThemedExport('contact-section', ContactSectionBase);
|
|
101
|
+
export const FooterHome = createThemedExport('footer-home', BaseFooterHome);
|
|
102
|
+
export const HeaderNavigation = createThemedExport('header-navigation', BaseHeaderNavigation);
|
|
103
|
+
export const GenericHeaderComponent = createThemedExport('generic-header-component', BaseGenericHeaderComponent);
|
|
104
|
+
export const StatisticsSection = createThemedExport('statistics-section', BaseStatisticsSection);
|
|
105
|
+
export const ValuesSection = createThemedExport('values-section', BaseValuesSection);
|
|
106
|
+
export const TeamGrid = createThemedExport('team-grid', BaseTeamGrid);
|
|
107
|
+
export const LocationGrid = createThemedExport('location-grid', BaseLocationGrid);
|
|
108
|
+
export const LocationDetailsSection = createThemedExport('location-details-section', BaseLocationDetailsSection);
|
|
109
|
+
export const ServicesGrid = createThemedExport('services-grid', BaseServicesGrid);
|
|
110
|
+
export const SocialMediaGrid = createThemedExport('social-media-grid', BaseSocialMediaGrid);
|
|
111
|
+
export const JobGallery = createThemedExport('job-gallery', BaseJobGallery);
|
|
112
|
+
export const BlogHome = createThemedExport('blog-home', BaseBlogHome);
|
|
113
|
+
export const BlogGallery = createThemedExport('blog-gallery', BaseBlogGallery);
|
|
114
|
+
export const BlogPostSection = createThemedExport('blog-post', BaseBlogPostSection);
|
|
115
|
+
export const ContactHome = createThemedExport('contact-home', BaseContactHome);
|
|
116
|
+
export const FAQGrid = createThemedExport('faq-grid', BaseFAQGrid);
|
|
117
|
+
export const FAQHero = createThemedExport('hero-faq', BaseFAQHero);
|
|
118
|
+
|
|
119
|
+
// Re-export component variants (these files contain multiple variants, not a single component)
|
|
120
|
+
export * from './blog-cards';
|
|
121
|
+
export * from './feature-tab';
|
|
122
|
+
export * from './feature-text';
|
|
123
|
+
export const GenericTextHero = createThemedExport('hero-generic-text', BaseGenericTextHero);
|
|
124
|
+
export const LocationDetailHero = createThemedExport('hero-location-detail', BaseLocationDetailHero);
|
|
125
|
+
export const ServiceDetailHero = createThemedExport('hero-service-detail', BaseServiceDetailHero);
|
|
126
|
+
export const SocialMediaHero = createThemedExport('hero-social-media', BaseSocialMediaHero);
|
|
127
|
+
export const TestimonialsHero = createThemedExport('hero-testimonials', BaseTestimonialsHero);
|
|
128
|
+
export const HomeHeroComponent = createThemedExport('home-hero-component', BaseHomeHeroComponent);
|
|
129
|
+
|
|
130
|
+
// Re-export types
|
|
131
|
+
export type { Theme } from '../../themes';
|