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,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface FAQHeroProps {
|
|
5
|
+
config: {
|
|
6
|
+
pages?: any[];
|
|
7
|
+
};
|
|
8
|
+
pageName?: string;
|
|
9
|
+
sectionKey?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const FAQHero = ({
|
|
13
|
+
config,
|
|
14
|
+
pageName = 'FAQ',
|
|
15
|
+
sectionKey = 'faq_page_section_1_hero',
|
|
16
|
+
}: FAQHeroProps) => {
|
|
17
|
+
const title = 'Frequently Asked Questions';
|
|
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-faq', 'aman', FAQHero);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { SearchLg } from '@untitledui/icons';
|
|
4
|
+
import { Input } from '../elements';
|
|
5
|
+
|
|
6
|
+
interface FAQHeroProps {
|
|
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 FAQHero = ({
|
|
17
|
+
config,
|
|
18
|
+
searchTerm,
|
|
19
|
+
onSearchChange,
|
|
20
|
+
}: FAQHeroProps) => {
|
|
21
|
+
// Extract values from config
|
|
22
|
+
const label = "Support";
|
|
23
|
+
const headline = "Frequently Asked Questions";
|
|
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"
|
|
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,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface GenericTextHeroProps {
|
|
5
|
+
title?: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
config?: {
|
|
8
|
+
pages?: any[];
|
|
9
|
+
};
|
|
10
|
+
pageName?: string;
|
|
11
|
+
sectionKey?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const GenericTextHero = ({
|
|
15
|
+
title: titleProp,
|
|
16
|
+
subtitle: subtitleProp,
|
|
17
|
+
config,
|
|
18
|
+
pageName,
|
|
19
|
+
sectionKey,
|
|
20
|
+
}: GenericTextHeroProps) => {
|
|
21
|
+
// Try to get from config first, then fall back to props
|
|
22
|
+
let title = titleProp;
|
|
23
|
+
let subtitle = subtitleProp;
|
|
24
|
+
|
|
25
|
+
if (config && pageName && sectionKey) {
|
|
26
|
+
title = titleProp || pageName || '';
|
|
27
|
+
subtitle = subtitleProp || '';
|
|
28
|
+
} else if (!title && pageName) {
|
|
29
|
+
title = pageName;
|
|
30
|
+
}
|
|
31
|
+
return (
|
|
32
|
+
<section className="py-24 md:py-32">
|
|
33
|
+
<div className="mx-auto max-w-4xl px-4 text-center md:px-8">
|
|
34
|
+
<h1 className="font-display text-5xl font-normal leading-tight text-fg-primary md:text-6xl lg:text-7xl">
|
|
35
|
+
{title}
|
|
36
|
+
</h1>
|
|
37
|
+
|
|
38
|
+
{subtitle && (
|
|
39
|
+
<p className="mt-6 font-display text-lg leading-relaxed text-tertiary md:text-xl max-w-3xl mx-auto">
|
|
40
|
+
{subtitle}
|
|
41
|
+
</p>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</section>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
import { registerThemeVariant } from '../../lib/component-registry';
|
|
49
|
+
registerThemeVariant('hero-generic-text', 'aman', GenericTextHero);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface GenericTextHeroProps {
|
|
5
|
+
// Config data - component extracts all values from it
|
|
6
|
+
config: {
|
|
7
|
+
pages?: any[];
|
|
8
|
+
};
|
|
9
|
+
pageName: string; // e.g., 'Blog', 'FAQ', 'Testimonials'
|
|
10
|
+
sectionKey: string; // e.g., 'blog_page_section_1_hero'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const GenericTextHero = ({
|
|
14
|
+
config,
|
|
15
|
+
pageName,
|
|
16
|
+
sectionKey,
|
|
17
|
+
}: GenericTextHeroProps) => {
|
|
18
|
+
// Extract values from config
|
|
19
|
+
const label = undefined;
|
|
20
|
+
const headline = "";
|
|
21
|
+
const subhead = "";
|
|
22
|
+
|
|
23
|
+
// Don't render if there's no content
|
|
24
|
+
if (!headline && !subhead && !label) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<section className="bg-primary py-16 md:py-24">
|
|
30
|
+
<div className="mx-auto max-w-container px-4 md:px-8">
|
|
31
|
+
<div className="mx-auto flex w-full max-w-3xl flex-col items-center text-center">
|
|
32
|
+
{label && (
|
|
33
|
+
<span className="text-sm font-semibold text-brand-secondary md:text-md">{label}</span>
|
|
34
|
+
)}
|
|
35
|
+
{headline && (
|
|
36
|
+
<h1 className={`${label ? 'mt-3' : ''} text-display-md font-semibold text-primary md:text-display-lg`}>
|
|
37
|
+
{headline}
|
|
38
|
+
</h1>
|
|
39
|
+
)}
|
|
40
|
+
{subhead && (
|
|
41
|
+
<p className="mt-4 text-lg text-tertiary md:mt-6 md:text-xl">
|
|
42
|
+
{subhead}
|
|
43
|
+
</p>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { PhotoWithFallback } from '../elements';
|
|
6
|
+
import type { WebsitePhotos } from '../../types/api/website-photos';
|
|
7
|
+
|
|
8
|
+
interface HeroHomeProps {
|
|
9
|
+
config?: any;
|
|
10
|
+
websitePhotos?: WebsitePhotos | null;
|
|
11
|
+
headline?: string;
|
|
12
|
+
subhead?: string;
|
|
13
|
+
ctaText?: string;
|
|
14
|
+
ctaHref?: string;
|
|
15
|
+
reviews?: { rating: number; count: number };
|
|
16
|
+
onEmailSubmit?: (email: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const HeroHome = ({
|
|
20
|
+
config,
|
|
21
|
+
websitePhotos,
|
|
22
|
+
headline,
|
|
23
|
+
subhead,
|
|
24
|
+
ctaText,
|
|
25
|
+
ctaHref = "/contact",
|
|
26
|
+
}: HeroHomeProps) => {
|
|
27
|
+
// Get section config for the hero
|
|
28
|
+
const homePageConfig = config?.pages?.find((p: any) =>
|
|
29
|
+
p.library_reference_name === 'Home' || p.slug === 'home'
|
|
30
|
+
);
|
|
31
|
+
const heroSection = homePageConfig?.sections?.home_page_section_1_hero;
|
|
32
|
+
|
|
33
|
+
// Use config values, then props, then defaults
|
|
34
|
+
const displayHeadline = headline || heroSection?.headline || '*TR* Your Slogan Here';
|
|
35
|
+
const displaySubhead = subhead || heroSection?.subhead || '*TR* A brief description of your business and what makes you special';
|
|
36
|
+
const displayCtaText = ctaText || heroSection?.cta_text || 'Discover more';
|
|
37
|
+
|
|
38
|
+
const heroImage = {
|
|
39
|
+
url: websitePhotos?.hero?.url || (websitePhotos as any)?.brand?.url || '',
|
|
40
|
+
alt: websitePhotos?.hero?.alt || (websitePhotos as any)?.brand?.alt || 'Hero image',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Fragment>
|
|
45
|
+
<section className="py-24 md:py-32">
|
|
46
|
+
<div className="mx-auto max-w-4xl px-4 text-center md:px-8">
|
|
47
|
+
<h1 className="font-display text-5xl font-normal leading-tight text-fg-primary md:text-6xl lg:text-7xl">
|
|
48
|
+
{displayHeadline}
|
|
49
|
+
</h1>
|
|
50
|
+
|
|
51
|
+
<p className="mt-6 font-body text-lg leading-relaxed text-tertiary md:text-xl max-w-3xl mx-auto">
|
|
52
|
+
{displaySubhead}
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
{displayCtaText && (
|
|
56
|
+
<a
|
|
57
|
+
href={ctaHref}
|
|
58
|
+
className="mt-8 inline-block font-body text-base underline underline-offset-4 hover:no-underline transition-colors"
|
|
59
|
+
style={{ color: 'var(--color-text-brand-accent)' }}
|
|
60
|
+
>
|
|
61
|
+
{displayCtaText}
|
|
62
|
+
</a>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
66
|
+
|
|
67
|
+
<section>
|
|
68
|
+
<div className="mx-auto max-w-container px-4 md:px-8">
|
|
69
|
+
<div className="w-full h-[400px] md:h-[500px] lg:h-[600px]">
|
|
70
|
+
<PhotoWithFallback
|
|
71
|
+
photoUrl={heroImage.url}
|
|
72
|
+
photoAlt={heroImage.alt}
|
|
73
|
+
fallbackId="hero-home-brand"
|
|
74
|
+
className="w-full h-full object-cover"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
</Fragment>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
import { registerThemeVariant } from '../../lib/component-registry';
|
|
84
|
+
registerThemeVariant('hero-home', 'aman', HeroHome);
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment, useMemo } from "react";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { Button, Form, Input, PhotoWithFallback } from '../elements';
|
|
6
|
+
import { cx } from '../../utils/cx';
|
|
7
|
+
import { mapIcon } from '../utils/icon-mapping';
|
|
8
|
+
import type { FC } from "react";
|
|
9
|
+
import type { WebsitePhotos } from '../../types/api/website-photos';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
interface HeroHomeProps {
|
|
13
|
+
config: {
|
|
14
|
+
pages?: any[];
|
|
15
|
+
};
|
|
16
|
+
websitePhotos?: WebsitePhotos | null;
|
|
17
|
+
reviews?: {
|
|
18
|
+
rating?: number;
|
|
19
|
+
count?: number;
|
|
20
|
+
};
|
|
21
|
+
onEmailSubmit?: (email: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const AvatarsWithReview = ({
|
|
25
|
+
rating = 5.0,
|
|
26
|
+
count = 200,
|
|
27
|
+
stockPhotos = [],
|
|
28
|
+
className
|
|
29
|
+
}: {
|
|
30
|
+
rating?: number;
|
|
31
|
+
count?: number;
|
|
32
|
+
stockPhotos?: any[];
|
|
33
|
+
className?: string;
|
|
34
|
+
}) => {
|
|
35
|
+
// Get up to 5 stock photos for avatars, cycling if needed
|
|
36
|
+
const avatarPhotos = useMemo(() => {
|
|
37
|
+
if (stockPhotos.length === 0) {
|
|
38
|
+
return Array(5).fill(null).map((_, i) => ({
|
|
39
|
+
url: undefined,
|
|
40
|
+
alt: `Avatar ${i + 1}`,
|
|
41
|
+
fallbackId: `avatar-${i + 1}`
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return Array(5).fill(null).map((_, i) => {
|
|
46
|
+
const photo = stockPhotos[i % stockPhotos.length];
|
|
47
|
+
return {
|
|
48
|
+
url: photo?.url || photo?.thumbnail_url,
|
|
49
|
+
alt: photo?.alt || `Avatar ${i + 1}`,
|
|
50
|
+
fallbackId: photo?.id || `avatar-${i + 1}`
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
}, [stockPhotos]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={cx("flex items-center gap-4", className)}>
|
|
57
|
+
<div className="inline-flex -space-x-3 overflow-hidden">
|
|
58
|
+
{avatarPhotos.map((avatar, index) => (
|
|
59
|
+
<div
|
|
60
|
+
key={index}
|
|
61
|
+
className="inline-block size-10 rounded-full ring-[1.5px] ring-bg-primary outline-1 -outline-offset-1 outline-avatar-contrast-border overflow-hidden"
|
|
62
|
+
>
|
|
63
|
+
<PhotoWithFallback
|
|
64
|
+
photoUrl={avatar.url}
|
|
65
|
+
photoAlt={avatar.alt}
|
|
66
|
+
fallbackId={avatar.fallbackId}
|
|
67
|
+
className="size-full object-cover"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
<div className="flex flex-col gap-0.5">
|
|
73
|
+
<div className="flex items-center gap-2">
|
|
74
|
+
<div className="flex items-center gap-1">
|
|
75
|
+
{Array(5)
|
|
76
|
+
.fill(null)
|
|
77
|
+
.map((_, index) => {
|
|
78
|
+
const clipId0 = `clip0_star_${index}`;
|
|
79
|
+
const clipId1 = `clip1_star_${index}`;
|
|
80
|
+
return (
|
|
81
|
+
<svg
|
|
82
|
+
key={index}
|
|
83
|
+
width={20}
|
|
84
|
+
height={20}
|
|
85
|
+
viewBox="0 0 20 20"
|
|
86
|
+
fill="none"
|
|
87
|
+
className="relative size-5 shrink-0 grow-0"
|
|
88
|
+
preserveAspectRatio="none"
|
|
89
|
+
>
|
|
90
|
+
<g clipPath={`url(#${clipId0})`}>
|
|
91
|
+
<path
|
|
92
|
+
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"
|
|
93
|
+
className="fill-bg-tertiary"
|
|
94
|
+
/>
|
|
95
|
+
<g clipPath={`url(#${clipId1})`}>
|
|
96
|
+
<path
|
|
97
|
+
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"
|
|
98
|
+
className="fill-warning-300"
|
|
99
|
+
/>
|
|
100
|
+
</g>
|
|
101
|
+
</g>
|
|
102
|
+
<defs>
|
|
103
|
+
<clipPath id={clipId0}>
|
|
104
|
+
<rect width={20} height={20} fill="white" />
|
|
105
|
+
</clipPath>
|
|
106
|
+
<clipPath id={clipId1}>
|
|
107
|
+
<rect width={20} height={20} fill="white" />
|
|
108
|
+
</clipPath>
|
|
109
|
+
</defs>
|
|
110
|
+
</svg>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<p className="text-md font-medium text-tertiary">from {count}+ reviews</p>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const HeroHome = ({
|
|
122
|
+
config,
|
|
123
|
+
websitePhotos,
|
|
124
|
+
reviews,
|
|
125
|
+
onEmailSubmit,
|
|
126
|
+
}: HeroHomeProps) => {
|
|
127
|
+
// Extract values from config
|
|
128
|
+
const headline = '';
|
|
129
|
+
const subhead = '';
|
|
130
|
+
const email_signup = undefined ? {
|
|
131
|
+
placeholder: heroSection.email_signup.placeholder || "Enter your email",
|
|
132
|
+
button_text: heroSection.email_signup.button_text || "Get started",
|
|
133
|
+
privacy_policy_link: heroSection.email_signup.privacy_policy_link || "#",
|
|
134
|
+
} : undefined;
|
|
135
|
+
const statistics = (undefined || []).map((stat: any) => ({
|
|
136
|
+
number: stat.number,
|
|
137
|
+
label: stat.label,
|
|
138
|
+
icon: stat.icon,
|
|
139
|
+
color: stat.color,
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
// Get hero image from props
|
|
143
|
+
const heroImage = {
|
|
144
|
+
url: websitePhotos?.hero?.url || '',
|
|
145
|
+
alt: websitePhotos?.hero?.alt || 'Hero image',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const stockPhotos = websitePhotos?.stock_photos || [];
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Fragment>
|
|
152
|
+
<section className="bg-primary py-16 md:pb-24">
|
|
153
|
+
<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">
|
|
154
|
+
<div className="flex max-w-3xl flex-col items-start lg:pr-8">
|
|
155
|
+
<h1 className="text-display-md font-semibold text-primary md:text-display-lg lg:text-display-xl">
|
|
156
|
+
{headline || 'Welcome'}
|
|
157
|
+
</h1>
|
|
158
|
+
<p className="mt-4 max-w-lg text-lg text-balance text-tertiary md:mt-6 md:text-xl">
|
|
159
|
+
{subhead || ''}
|
|
160
|
+
</p>
|
|
161
|
+
|
|
162
|
+
{email_signup && (
|
|
163
|
+
<Form
|
|
164
|
+
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
const formData = new FormData(e.currentTarget);
|
|
167
|
+
const email = formData.get("email") as string;
|
|
168
|
+
if (onEmailSubmit) {
|
|
169
|
+
onEmailSubmit(email);
|
|
170
|
+
}
|
|
171
|
+
}}
|
|
172
|
+
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"
|
|
173
|
+
>
|
|
174
|
+
<Input
|
|
175
|
+
isRequired
|
|
176
|
+
size="md"
|
|
177
|
+
name="email"
|
|
178
|
+
type="email"
|
|
179
|
+
placeholder={email_signup.placeholder}
|
|
180
|
+
wrapperClassName="py-0.5"
|
|
181
|
+
hint={
|
|
182
|
+
<span>
|
|
183
|
+
We care about your data in our{" "}
|
|
184
|
+
<a
|
|
185
|
+
href={email_signup.privacy_policy_link}
|
|
186
|
+
className="rounded-xs underline underline-offset-3 outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
187
|
+
>
|
|
188
|
+
privacy policy
|
|
189
|
+
</a>
|
|
190
|
+
.
|
|
191
|
+
</span>
|
|
192
|
+
}
|
|
193
|
+
/>
|
|
194
|
+
<Button type="submit" color="primary" size="xl">
|
|
195
|
+
{email_signup.button_text}
|
|
196
|
+
</Button>
|
|
197
|
+
</Form>
|
|
198
|
+
)}
|
|
199
|
+
|
|
200
|
+
<AvatarsWithReview
|
|
201
|
+
rating={reviews?.rating}
|
|
202
|
+
count={reviews?.count}
|
|
203
|
+
stockPhotos={stockPhotos}
|
|
204
|
+
className="mt-8 md:mt-12"
|
|
205
|
+
/>
|
|
206
|
+
|
|
207
|
+
{(() => {
|
|
208
|
+
const statsToShow = statistics && statistics.length > 0 ? statistics.slice(0, 4) : [];
|
|
209
|
+
|
|
210
|
+
return statsToShow.length > 0 && (
|
|
211
|
+
<dl className="mt-8 grid grid-cols-2 gap-x-6 gap-y-4 md:mt-12">
|
|
212
|
+
{statsToShow.map((stat: { number: string; label: string; icon?: string | FC<{ className?: string }> | React.ReactNode; color?: string }, index: number) => {
|
|
213
|
+
const IconComponent = mapIcon(stat.icon);
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<div key={index} className="flex flex-col gap-2">
|
|
217
|
+
<div className="flex items-center gap-2">
|
|
218
|
+
{IconComponent && (
|
|
219
|
+
<div className="size-5 shrink-0">
|
|
220
|
+
<IconComponent className="w-full h-full" />
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
<dd className="text-lg font-semibold text-primary md:text-xl">{stat.number}</dd>
|
|
224
|
+
</div>
|
|
225
|
+
<dt className="text-sm font-medium text-tertiary md:text-md">{stat.label}</dt>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
})}
|
|
229
|
+
</dl>
|
|
230
|
+
);
|
|
231
|
+
})()}
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<div className="relative lg:h-full lg:min-h-160">
|
|
235
|
+
<PhotoWithFallback
|
|
236
|
+
photoUrl={heroImage.url}
|
|
237
|
+
photoAlt={heroImage.alt}
|
|
238
|
+
fallbackId="hero-home-image"
|
|
239
|
+
className="inset-0 h-70 w-full object-cover md:h-110 lg:absolute lg:h-full"
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</section>
|
|
244
|
+
</Fragment>
|
|
245
|
+
);
|
|
246
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
interface LocationDetailHeroProps {
|
|
4
|
+
location?: any;
|
|
5
|
+
config?: {
|
|
6
|
+
pages?: any[];
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const LocationDetailHero = ({
|
|
11
|
+
location,
|
|
12
|
+
}: LocationDetailHeroProps) => {
|
|
13
|
+
if (!location) return null;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<section className="py-24 md:py-32">
|
|
17
|
+
<div className="mx-auto max-w-4xl px-4 text-center md:px-8">
|
|
18
|
+
<h1 className="font-display text-5xl font-normal leading-tight text-fg-primary md:text-6xl lg:text-7xl">
|
|
19
|
+
{location.name}
|
|
20
|
+
</h1>
|
|
21
|
+
|
|
22
|
+
{location.city && location.state && (
|
|
23
|
+
<p className="mt-6 text-xs font-body uppercase tracking-widest" style={{ color: 'var(--color-text-brand-secondary)' }}>
|
|
24
|
+
{location.city}, {location.state}
|
|
25
|
+
</p>
|
|
26
|
+
)}
|
|
27
|
+
</div>
|
|
28
|
+
</section>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
import { registerThemeVariant } from '../../lib/component-registry';
|
|
33
|
+
registerThemeVariant('hero-location-detail', 'aman', LocationDetailHero);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronLeft } from "@untitledui/icons";
|
|
4
|
+
import { Button } from '../elements';
|
|
5
|
+
import type { Location } from '../../types/api/location';
|
|
6
|
+
|
|
7
|
+
interface LocationDetailHeroProps {
|
|
8
|
+
config: {
|
|
9
|
+
pages?: any[];
|
|
10
|
+
};
|
|
11
|
+
location?: Location | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const LocationDetailHero = ({
|
|
15
|
+
config,
|
|
16
|
+
location,
|
|
17
|
+
}: LocationDetailHeroProps) => {
|
|
18
|
+
if (!location) return null;
|
|
19
|
+
|
|
20
|
+
const breadcrumb = {
|
|
21
|
+
backHref: "/locations",
|
|
22
|
+
backLabel: "Locations",
|
|
23
|
+
currentLabel: location.name || "Location",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const headline = location.name || "Location";
|
|
27
|
+
const subhead = location.description_markdown?.replace(/[#*\[\]()]/g, '').trim() || "";
|
|
28
|
+
const ctaText = "Contact Us";
|
|
29
|
+
const ctaHref = "/contact";
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<section className="bg-primary py-4 md:py-6">
|
|
33
|
+
<div className="mx-auto max-w-container px-4 md:px-8">
|
|
34
|
+
{breadcrumb && (
|
|
35
|
+
<div className="mb-8">
|
|
36
|
+
<nav aria-label="Breadcrumb">
|
|
37
|
+
<div className="flex items-center gap-2">
|
|
38
|
+
<Button
|
|
39
|
+
color="link-gray"
|
|
40
|
+
size="md"
|
|
41
|
+
href={breadcrumb.backHref}
|
|
42
|
+
iconLeading={ChevronLeft}
|
|
43
|
+
className="text-tertiary hover:text-primary"
|
|
44
|
+
>
|
|
45
|
+
{breadcrumb.backLabel}
|
|
46
|
+
</Button>
|
|
47
|
+
<span className="text-tertiary">/</span>
|
|
48
|
+
<span className="text-primary font-medium">{breadcrumb.currentLabel}</span>
|
|
49
|
+
</div>
|
|
50
|
+
</nav>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
<div className="mx-auto flex w-full max-w-3xl flex-col items-center text-center">
|
|
55
|
+
<h1 className="text-display-md font-semibold text-primary md:text-display-lg">
|
|
56
|
+
{headline}
|
|
57
|
+
</h1>
|
|
58
|
+
|
|
59
|
+
<p className="mt-4 text-lg text-tertiary md:mt-6 md:text-xl">
|
|
60
|
+
{subhead}
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<div className="mt-8">
|
|
64
|
+
<Button size="xl" href={ctaHref}>
|
|
65
|
+
{ctaText}
|
|
66
|
+
</Button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</section>
|
|
71
|
+
);
|
|
72
|
+
};
|