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,848 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
import { ArrowUpRight } from "@untitledui/icons";
|
|
5
|
+
import { Avatar, BadgeGroup, Badge, Button, PhotoWithFallback } from '../elements';
|
|
6
|
+
import { cx } from '../../utils/cx';
|
|
7
|
+
import { getAvatarUrl } from '../../utils/photo-helpers';
|
|
8
|
+
import type { BlogPost } from '../../types/api/blog-post';
|
|
9
|
+
|
|
10
|
+
// Helper to get first author from blog post
|
|
11
|
+
const getFirstAuthor = (post: BlogPost) => {
|
|
12
|
+
return Array.isArray(post.blog_post_authors) && post.blog_post_authors.length > 0
|
|
13
|
+
? post.blog_post_authors[0]
|
|
14
|
+
: null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Helper to format published date
|
|
18
|
+
const formatPublishedDate = (publishedAt?: string) => {
|
|
19
|
+
return publishedAt
|
|
20
|
+
? new Date(publishedAt).toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' })
|
|
21
|
+
: 'Recent';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Helper to clean excerpt markdown
|
|
25
|
+
const cleanExcerpt = (excerpt?: string) => {
|
|
26
|
+
return excerpt?.replace(/[#*\[\]()]/g, '').trim() || '';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Helper to get first tag from blog post
|
|
30
|
+
const getFirstTag = (post: BlogPost) => {
|
|
31
|
+
return Array.isArray(post.blog_post_tags) && post.blog_post_tags.length > 0
|
|
32
|
+
? post.blog_post_tags[0]
|
|
33
|
+
: null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Helper to extract all blog post data for rendering
|
|
37
|
+
const getBlogPostData = (article: BlogPost) => {
|
|
38
|
+
const author = getFirstAuthor(article);
|
|
39
|
+
const authorName = author?.name || 'Author';
|
|
40
|
+
const authorHref = author?.slug ? `/blog/author/${author.slug}` : '/blog';
|
|
41
|
+
const authorAvatarUrl = getAvatarUrl(author?.photo_attachments, author?.id, authorName);
|
|
42
|
+
const tag = getFirstTag(article);
|
|
43
|
+
const tagName = tag?.name || 'Blog';
|
|
44
|
+
const tagHref = tag?.slug ? `/blog/tag/${tag.slug}` : '/blog';
|
|
45
|
+
const href = `/blog/${article.slug}`;
|
|
46
|
+
const title = article.title || 'Untitled Post';
|
|
47
|
+
const publishedAt = formatPublishedDate(article.published_at);
|
|
48
|
+
const summary = cleanExcerpt(article.excerpt_markdown);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
author,
|
|
52
|
+
authorName,
|
|
53
|
+
authorHref,
|
|
54
|
+
authorAvatarUrl,
|
|
55
|
+
tag,
|
|
56
|
+
tagName,
|
|
57
|
+
tagHref,
|
|
58
|
+
href,
|
|
59
|
+
title,
|
|
60
|
+
publishedAt,
|
|
61
|
+
summary,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const BlogCardVertical = ({ article, imageClassName }: { article: BlogPost; imageClassName?: string }) => {
|
|
66
|
+
const { authorName, authorHref, authorAvatarUrl, href, title, summary, publishedAt } = getBlogPostData(article);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<article className="flex flex-col gap-4">
|
|
70
|
+
<a href={href} className="overflow-hidden rounded-2xl" tabIndex={-1}>
|
|
71
|
+
<PhotoWithFallback
|
|
72
|
+
item={article}
|
|
73
|
+
fallbackId={article.id}
|
|
74
|
+
alt={title}
|
|
75
|
+
className={cx("aspect-[1.5] w-full object-cover transition duration-100 ease-linear hover:scale-105", imageClassName)}
|
|
76
|
+
/>
|
|
77
|
+
</a>
|
|
78
|
+
|
|
79
|
+
<div className="flex flex-col gap-5">
|
|
80
|
+
<div className="flex flex-col gap-2">
|
|
81
|
+
<span className="text-sm font-semibold text-brand-secondary">Blog</span>
|
|
82
|
+
<div className="flex flex-col gap-1">
|
|
83
|
+
<a
|
|
84
|
+
href={href}
|
|
85
|
+
className="group/title flex justify-between gap-x-4 rounded-md text-lg font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
86
|
+
>
|
|
87
|
+
{title}
|
|
88
|
+
<ArrowUpRight
|
|
89
|
+
className="mt-0.5 size-6 shrink-0 text-fg-quaternary transition duration-100 ease-linear group-hover/title:text-fg-quaternary_hover"
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
/>
|
|
92
|
+
</a>
|
|
93
|
+
|
|
94
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div className="flex gap-2">
|
|
99
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
100
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
101
|
+
</a>
|
|
102
|
+
|
|
103
|
+
<div>
|
|
104
|
+
<a
|
|
105
|
+
href={authorHref}
|
|
106
|
+
className="block rounded-xs text-sm font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
107
|
+
>
|
|
108
|
+
{authorName}
|
|
109
|
+
</a>
|
|
110
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</article>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const BlogCardVerticalBadge = ({
|
|
119
|
+
article,
|
|
120
|
+
badgeTheme = "light",
|
|
121
|
+
imageClassName,
|
|
122
|
+
}: {
|
|
123
|
+
article: BlogPost;
|
|
124
|
+
badgeTheme?: ComponentProps<typeof BadgeGroup>["theme"];
|
|
125
|
+
imageClassName?: string;
|
|
126
|
+
}) => {
|
|
127
|
+
const { authorName, authorHref, authorAvatarUrl, href, title, summary, publishedAt } = getBlogPostData(article);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<article className="flex flex-col gap-4">
|
|
131
|
+
<a href={href} className="overflow-hidden rounded-xl" tabIndex={-1}>
|
|
132
|
+
<PhotoWithFallback
|
|
133
|
+
item={article}
|
|
134
|
+
fallbackId={article.id}
|
|
135
|
+
alt={title}
|
|
136
|
+
className={cx("aspect-[1.5] w-full object-cover", imageClassName)}
|
|
137
|
+
/>
|
|
138
|
+
</a>
|
|
139
|
+
|
|
140
|
+
<div className="flex flex-col gap-5">
|
|
141
|
+
<div className="flex flex-col items-start gap-3">
|
|
142
|
+
<BadgeGroup addonText="Blog" size="md" theme={badgeTheme} color="brand" className="pr-3" iconTrailing={null}>
|
|
143
|
+
5 min read
|
|
144
|
+
</BadgeGroup>
|
|
145
|
+
<div className="flex flex-col gap-1">
|
|
146
|
+
<a
|
|
147
|
+
href={href}
|
|
148
|
+
className="flex justify-between gap-x-4 rounded-md text-lg font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
149
|
+
>
|
|
150
|
+
{title}
|
|
151
|
+
<ArrowUpRight className="mt-0.5 size-6 shrink-0 text-fg-quaternary" aria-hidden="true" />
|
|
152
|
+
</a>
|
|
153
|
+
<p className="line-clamp-2 text-md text-tertiary md:line-clamp-none">{summary}</p>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div className="flex gap-2">
|
|
158
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
159
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
160
|
+
</a>
|
|
161
|
+
|
|
162
|
+
<div>
|
|
163
|
+
<a
|
|
164
|
+
href={authorHref}
|
|
165
|
+
className="block rounded-xs text-sm font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
166
|
+
>
|
|
167
|
+
{authorName}
|
|
168
|
+
</a>
|
|
169
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</article>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const BlogCardVerticalCompact = ({
|
|
178
|
+
article,
|
|
179
|
+
imageClassName,
|
|
180
|
+
titleClassName,
|
|
181
|
+
className,
|
|
182
|
+
}: {
|
|
183
|
+
article: BlogPost;
|
|
184
|
+
imageClassName?: string;
|
|
185
|
+
titleClassName?: string;
|
|
186
|
+
className?: string;
|
|
187
|
+
}) => {
|
|
188
|
+
const { authorName, href, title, summary, publishedAt } = getBlogPostData(article);
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<article className={cx("flex flex-col gap-4", className)}>
|
|
192
|
+
<a href={href} className="overflow-hidden rounded-2xl" tabIndex={-1}>
|
|
193
|
+
<PhotoWithFallback
|
|
194
|
+
item={article}
|
|
195
|
+
fallbackId={article.id}
|
|
196
|
+
alt={title}
|
|
197
|
+
className={cx("aspect-[1.5] w-full object-cover", imageClassName)}
|
|
198
|
+
/>
|
|
199
|
+
</a>
|
|
200
|
+
|
|
201
|
+
<div className="flex flex-col gap-6">
|
|
202
|
+
<div className="flex flex-col items-start gap-2">
|
|
203
|
+
<p className="text-sm font-semibold text-brand-secondary">
|
|
204
|
+
{authorName} • <time>{publishedAt}</time>
|
|
205
|
+
</p>
|
|
206
|
+
<div className="flex w-full flex-col gap-1">
|
|
207
|
+
<a
|
|
208
|
+
href={href}
|
|
209
|
+
className={cx(
|
|
210
|
+
"flex justify-between gap-x-4 rounded-md text-lg font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
211
|
+
titleClassName,
|
|
212
|
+
)}
|
|
213
|
+
>
|
|
214
|
+
{title}
|
|
215
|
+
<ArrowUpRight className="mt-0.5 size-6 shrink-0 text-fg-quaternary" aria-hidden="true" />
|
|
216
|
+
</a>
|
|
217
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</article>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export const BlogCardVerticalMinimal = ({ article, imageClassName, className }: { article: BlogPost; imageClassName?: string; className?: string }) => {
|
|
226
|
+
const { authorName, authorHref, href, title, summary, publishedAt } = getBlogPostData(article);
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<article className={cx("flex flex-col gap-4", className)}>
|
|
230
|
+
<div className="relative">
|
|
231
|
+
<a href={href} className="w-full" tabIndex={-1}>
|
|
232
|
+
<PhotoWithFallback
|
|
233
|
+
item={article}
|
|
234
|
+
fallbackId={article.id}
|
|
235
|
+
alt={title}
|
|
236
|
+
className={cx("aspect-[1.5] w-full object-cover", imageClassName)}
|
|
237
|
+
/>
|
|
238
|
+
</a>
|
|
239
|
+
<div className="absolute inset-x-0 bottom-0 overflow-hidden bg-linear-to-b from-transparent to-black/40">
|
|
240
|
+
<div className="relative flex items-start justify-between bg-alpha-white/30 p-4 backdrop-blur-md before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-alpha-white/30 md:p-5">
|
|
241
|
+
<div>
|
|
242
|
+
<a
|
|
243
|
+
href={authorHref}
|
|
244
|
+
className="block rounded-xs text-sm font-semibold text-white outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
245
|
+
>
|
|
246
|
+
{authorName}
|
|
247
|
+
</a>
|
|
248
|
+
<time className="block text-sm text-white">{publishedAt}</time>
|
|
249
|
+
</div>
|
|
250
|
+
<a
|
|
251
|
+
href={href}
|
|
252
|
+
className="rounded-xs text-sm font-semibold text-white outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
253
|
+
>
|
|
254
|
+
Blog
|
|
255
|
+
</a>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className="flex flex-col items-start gap-5">
|
|
261
|
+
<div className="flex flex-col gap-1">
|
|
262
|
+
<a
|
|
263
|
+
href={href}
|
|
264
|
+
className="flex justify-between gap-x-4 rounded-md text-lg font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
265
|
+
>
|
|
266
|
+
{title}
|
|
267
|
+
</a>
|
|
268
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<Button href={href} color="link-color" size="lg" iconTrailing={ArrowUpRight}>
|
|
272
|
+
Read post
|
|
273
|
+
</Button>
|
|
274
|
+
</div>
|
|
275
|
+
</article>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export const BlogCardHorizontal = ({ article, imageClassName }: { article: BlogPost; imageClassName?: string }) => {
|
|
280
|
+
const { authorName, authorHref, authorAvatarUrl, tagName, tagHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<article className="flex flex-col gap-4 xl:flex-row xl:items-start">
|
|
284
|
+
<a href={href} className="shrink-0 overflow-hidden rounded-2xl" tabIndex={-1}>
|
|
285
|
+
<PhotoWithFallback
|
|
286
|
+
item={article}
|
|
287
|
+
fallbackId={article.id}
|
|
288
|
+
alt={title}
|
|
289
|
+
className={cx("aspect-[1.5] w-full object-cover xl:w-80", imageClassName)}
|
|
290
|
+
/>
|
|
291
|
+
</a>
|
|
292
|
+
|
|
293
|
+
<div className="flex flex-col gap-5">
|
|
294
|
+
<div className="flex flex-col gap-2">
|
|
295
|
+
<span className="text-sm font-semibold text-brand-secondary">{tagName}</span>
|
|
296
|
+
|
|
297
|
+
<div className="flex flex-col gap-1">
|
|
298
|
+
<a
|
|
299
|
+
href={href}
|
|
300
|
+
className="flex justify-between gap-x-4 rounded-md text-lg font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
301
|
+
>
|
|
302
|
+
{title}
|
|
303
|
+
</a>
|
|
304
|
+
|
|
305
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<div className="flex gap-2">
|
|
310
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
311
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
312
|
+
</a>
|
|
313
|
+
|
|
314
|
+
<div>
|
|
315
|
+
<a
|
|
316
|
+
href={authorHref}
|
|
317
|
+
className="block rounded-xs text-sm font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
318
|
+
>
|
|
319
|
+
{authorName}
|
|
320
|
+
</a>
|
|
321
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</article>
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export const BlogCardHorizontalBadge = ({ article }: { article: BlogPost }) => {
|
|
330
|
+
const { authorName, authorHref, authorAvatarUrl, tagName, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<article className="flex flex-col gap-5 lg:flex-row lg:items-start">
|
|
334
|
+
<a href={href} className="shrink-0 overflow-hidden" tabIndex={-1}>
|
|
335
|
+
<PhotoWithFallback
|
|
336
|
+
item={article}
|
|
337
|
+
fallbackId={article.id}
|
|
338
|
+
alt={title}
|
|
339
|
+
className="h-60 w-full object-cover lg:h-50 lg:w-91.5"
|
|
340
|
+
/>
|
|
341
|
+
</a>
|
|
342
|
+
|
|
343
|
+
<div className="flex flex-col gap-6">
|
|
344
|
+
<div className="flex flex-col gap-2">
|
|
345
|
+
<BadgeGroup addonText={tagName} size="md" theme="light" color="brand" className="pr-3" iconTrailing={null}>
|
|
346
|
+
Blog
|
|
347
|
+
</BadgeGroup>
|
|
348
|
+
<div className="flex flex-col gap-2">
|
|
349
|
+
<a
|
|
350
|
+
href={href}
|
|
351
|
+
className="rounded-xs text-lg font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
352
|
+
>
|
|
353
|
+
{title}
|
|
354
|
+
</a>
|
|
355
|
+
|
|
356
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<div className="flex gap-2">
|
|
361
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
362
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
363
|
+
</a>
|
|
364
|
+
|
|
365
|
+
<div>
|
|
366
|
+
<a
|
|
367
|
+
href={authorHref}
|
|
368
|
+
className="block rounded-xs text-sm font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
369
|
+
>
|
|
370
|
+
{authorName}
|
|
371
|
+
</a>
|
|
372
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</article>
|
|
377
|
+
);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
export const BlogCardHorizontalCompact = ({ article, imageClassName }: { article: BlogPost; imageClassName?: string }) => {
|
|
381
|
+
const { authorName, authorHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<article className="flex flex-col gap-4 xl:flex-row xl:items-start">
|
|
385
|
+
<a href={href} className="shrink-0 overflow-hidden rounded-2xl" tabIndex={-1}>
|
|
386
|
+
<PhotoWithFallback
|
|
387
|
+
item={article}
|
|
388
|
+
fallbackId={article.id}
|
|
389
|
+
alt={title}
|
|
390
|
+
className={cx("aspect-[1.5] w-full object-cover xl:w-91.5", imageClassName)}
|
|
391
|
+
/>
|
|
392
|
+
</a>
|
|
393
|
+
|
|
394
|
+
<div className="flex flex-col gap-6">
|
|
395
|
+
<div className="flex flex-col gap-2">
|
|
396
|
+
<p className="text-sm font-semibold text-brand-secondary">
|
|
397
|
+
<a href={authorHref} className="rounded-xs outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2">
|
|
398
|
+
{authorName}
|
|
399
|
+
</a>{" "}
|
|
400
|
+
• <time>{publishedAt}</time>
|
|
401
|
+
</p>
|
|
402
|
+
<div className="flex flex-col gap-1">
|
|
403
|
+
<a
|
|
404
|
+
href={href}
|
|
405
|
+
className="rounded-xs text-lg font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
406
|
+
>
|
|
407
|
+
{title}
|
|
408
|
+
</a>
|
|
409
|
+
|
|
410
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</article>
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
export const BlogCardHorizontalMinimal = ({ article }: { article: BlogPost }) => {
|
|
419
|
+
const { authorName, authorHref, tagName, tagHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
420
|
+
|
|
421
|
+
return (
|
|
422
|
+
<article className="flex flex-col gap-5 lg:flex-row lg:items-start">
|
|
423
|
+
<div className="relative shrink-0">
|
|
424
|
+
<a href={href} className="w-full" tabIndex={-1}>
|
|
425
|
+
<PhotoWithFallback
|
|
426
|
+
item={article}
|
|
427
|
+
fallbackId={article.id}
|
|
428
|
+
alt={title}
|
|
429
|
+
className="h-60 w-full object-cover lg:h-50 lg:w-80"
|
|
430
|
+
/>
|
|
431
|
+
</a>
|
|
432
|
+
<div className="absolute inset-x-0 bottom-0 overflow-hidden bg-linear-to-b from-transparent to-black/40">
|
|
433
|
+
<div className="relative flex items-start justify-between bg-alpha-white/30 p-4 backdrop-blur-md before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-alpha-white/30">
|
|
434
|
+
<div>
|
|
435
|
+
<a
|
|
436
|
+
href={authorHref}
|
|
437
|
+
className="block rounded-xs text-sm font-semibold text-white outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
438
|
+
>
|
|
439
|
+
{authorName}
|
|
440
|
+
</a>
|
|
441
|
+
<time className="block text-sm text-white">{publishedAt}</time>
|
|
442
|
+
</div>
|
|
443
|
+
<a
|
|
444
|
+
href={tagHref}
|
|
445
|
+
className="rounded-xs text-sm font-semibold text-white outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
446
|
+
>
|
|
447
|
+
{tagName}
|
|
448
|
+
</a>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<div className="flex flex-col items-start gap-6">
|
|
454
|
+
<div className="flex flex-col gap-2">
|
|
455
|
+
<a
|
|
456
|
+
href={href}
|
|
457
|
+
className="block rounded-xs text-xl font-semibold text-primary outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2 md:text-lg"
|
|
458
|
+
>
|
|
459
|
+
{title}
|
|
460
|
+
</a>
|
|
461
|
+
<p className="line-clamp-2 text-md text-tertiary lg:line-clamp-3">{summary}</p>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<Button href={href} color="link-color" size="lg" iconTrailing={ArrowUpRight}>
|
|
465
|
+
Read post
|
|
466
|
+
</Button>
|
|
467
|
+
</div>
|
|
468
|
+
</article>
|
|
469
|
+
);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
export const BlogCardFullWidthVertical = ({ article }: { article: BlogPost }) => {
|
|
473
|
+
const { authorName, authorHref, authorAvatarUrl, tagName, tagHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
474
|
+
|
|
475
|
+
return (
|
|
476
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset">
|
|
477
|
+
<a href={href} tabIndex={-1}>
|
|
478
|
+
<PhotoWithFallback
|
|
479
|
+
item={article}
|
|
480
|
+
fallbackId={article.id}
|
|
481
|
+
alt={title}
|
|
482
|
+
className="h-50 w-full object-cover md:h-60"
|
|
483
|
+
/>
|
|
484
|
+
</a>
|
|
485
|
+
|
|
486
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
487
|
+
<div className="flex flex-col gap-2">
|
|
488
|
+
<Button color="link-color" href={tagHref}>
|
|
489
|
+
{tagName}
|
|
490
|
+
</Button>
|
|
491
|
+
<div className="flex flex-col gap-2">
|
|
492
|
+
<Button
|
|
493
|
+
color="link-gray"
|
|
494
|
+
href={href}
|
|
495
|
+
className="flex justify-between gap-4 text-xl font-semibold text-primary hover:text-brand-secondary md:text-display-xs"
|
|
496
|
+
iconTrailing={<ArrowUpRight className="size-6 shrink-0" aria-hidden="true" />}
|
|
497
|
+
>
|
|
498
|
+
{title}
|
|
499
|
+
</Button>
|
|
500
|
+
<p className="line-clamp-2 text-md text-tertiary md:line-clamp-3">{summary}</p>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div className="flex gap-2">
|
|
505
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
506
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
507
|
+
</a>
|
|
508
|
+
|
|
509
|
+
<div>
|
|
510
|
+
<p className="text-sm font-semibold">
|
|
511
|
+
<Button color="link-color" href={authorHref} className="text-primary">
|
|
512
|
+
{authorName}
|
|
513
|
+
</Button>
|
|
514
|
+
</p>
|
|
515
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
</article>
|
|
520
|
+
);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
export const BlogCardFullWidthVerticalAlt = ({ article }: { article: BlogPost }) => {
|
|
524
|
+
const { authorName, authorHref, authorAvatarUrl, tagName, tagHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
525
|
+
|
|
526
|
+
return (
|
|
527
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset">
|
|
528
|
+
<a href={href} tabIndex={-1}>
|
|
529
|
+
<PhotoWithFallback
|
|
530
|
+
item={article}
|
|
531
|
+
fallbackId={article.id}
|
|
532
|
+
alt={title}
|
|
533
|
+
className="h-60 w-full object-cover"
|
|
534
|
+
/>
|
|
535
|
+
</a>
|
|
536
|
+
|
|
537
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
538
|
+
<div className="flex flex-col gap-4">
|
|
539
|
+
<BadgeGroup addonText={tagName} size="md" theme="light" color="brand" className="pr-3" iconTrailing={null}>
|
|
540
|
+
Blog
|
|
541
|
+
</BadgeGroup>
|
|
542
|
+
<div className="flex flex-col gap-2">
|
|
543
|
+
<Button
|
|
544
|
+
color="link-gray"
|
|
545
|
+
href={href}
|
|
546
|
+
className="flex justify-between gap-4 text-xl font-semibold text-primary hover:text-brand-secondary md:text-display-xs"
|
|
547
|
+
iconTrailing={<ArrowUpRight className="size-6 shrink-0" aria-hidden="true" />}
|
|
548
|
+
>
|
|
549
|
+
{title}
|
|
550
|
+
</Button>
|
|
551
|
+
<p className="line-clamp-2 text-md text-tertiary md:line-clamp-3">{summary}</p>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<div className="flex gap-2">
|
|
556
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
557
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
558
|
+
</a>
|
|
559
|
+
|
|
560
|
+
<div>
|
|
561
|
+
<p className="text-sm font-semibold">
|
|
562
|
+
<Button color="link-color" href={authorHref} className="text-primary">
|
|
563
|
+
{authorName}
|
|
564
|
+
</Button>
|
|
565
|
+
</p>
|
|
566
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
</article>
|
|
571
|
+
);
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export const BlogCardFullWidthVerticalCompact = ({ article }: { article: BlogPost }) => {
|
|
575
|
+
const { authorName, authorHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset">
|
|
579
|
+
<a href={href} tabIndex={-1}>
|
|
580
|
+
<PhotoWithFallback
|
|
581
|
+
item={article}
|
|
582
|
+
fallbackId={article.id}
|
|
583
|
+
alt={title}
|
|
584
|
+
className="h-60 w-full object-cover"
|
|
585
|
+
/>
|
|
586
|
+
</a>
|
|
587
|
+
|
|
588
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
589
|
+
<div className="flex flex-col gap-2">
|
|
590
|
+
<p className="text-sm font-semibold text-brand-secondary">
|
|
591
|
+
<Button href={authorHref} color="link-color">
|
|
592
|
+
{authorName}
|
|
593
|
+
</Button>{" "}
|
|
594
|
+
• <time>{publishedAt}</time>
|
|
595
|
+
</p>
|
|
596
|
+
<div className="flex flex-col gap-2">
|
|
597
|
+
<Button
|
|
598
|
+
color="link-gray"
|
|
599
|
+
href={href}
|
|
600
|
+
className="flex justify-between gap-4 text-xl font-semibold text-primary hover:text-brand-secondary md:text-display-xs"
|
|
601
|
+
iconTrailing={<ArrowUpRight className="size-6 shrink-0" aria-hidden="true" />}
|
|
602
|
+
>
|
|
603
|
+
{title}
|
|
604
|
+
</Button>
|
|
605
|
+
<p className="line-clamp-2 text-md text-tertiary md:line-clamp-3">{summary}</p>
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
</article>
|
|
610
|
+
);
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
export const BlogCardFullWidthVerticalMinimal = ({ article }: { article: BlogPost }) => {
|
|
614
|
+
const { authorName, authorHref, tagName, tagHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
615
|
+
|
|
616
|
+
return (
|
|
617
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset">
|
|
618
|
+
<div className="relative shrink-0">
|
|
619
|
+
<a href={href} className="w-full" tabIndex={-1}>
|
|
620
|
+
<PhotoWithFallback
|
|
621
|
+
item={article}
|
|
622
|
+
fallbackId={article.id}
|
|
623
|
+
alt={title}
|
|
624
|
+
className="h-60 w-full object-cover md:h-70"
|
|
625
|
+
/>
|
|
626
|
+
</a>
|
|
627
|
+
<div className="absolute inset-x-0 bottom-0 overflow-hidden bg-linear-to-b from-transparent to-black/40">
|
|
628
|
+
<div className="relative flex items-start justify-between bg-alpha-white/30 p-4 backdrop-blur-md before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-alpha-white/30 md:p-6">
|
|
629
|
+
<div>
|
|
630
|
+
<p className="text-sm font-semibold">
|
|
631
|
+
<Button href={authorHref} color="link-gray" className="text-white">
|
|
632
|
+
{authorName}
|
|
633
|
+
</Button>
|
|
634
|
+
</p>
|
|
635
|
+
<time className="block text-sm text-white">{publishedAt}</time>
|
|
636
|
+
</div>
|
|
637
|
+
<p className="text-sm font-semibold">
|
|
638
|
+
<Button href={tagHref} color="link-gray" className="text-white">
|
|
639
|
+
{tagName}
|
|
640
|
+
</Button>
|
|
641
|
+
</p>
|
|
642
|
+
</div>
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
647
|
+
<div className="flex flex-col gap-2">
|
|
648
|
+
<div className="flex flex-col gap-2">
|
|
649
|
+
<Button
|
|
650
|
+
color="link-gray"
|
|
651
|
+
href={href}
|
|
652
|
+
className="flex justify-between gap-4 text-xl font-semibold text-primary hover:text-brand-secondary md:text-display-xs"
|
|
653
|
+
iconTrailing={<ArrowUpRight className="size-6 shrink-0" aria-hidden="true" />}
|
|
654
|
+
>
|
|
655
|
+
{title}
|
|
656
|
+
</Button>
|
|
657
|
+
<p className="line-clamp-2 text-md text-tertiary md:line-clamp-3">{summary}</p>
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
<Button href={href} color="link-color" size="lg" iconTrailing={ArrowUpRight}>
|
|
662
|
+
Read post
|
|
663
|
+
</Button>
|
|
664
|
+
</div>
|
|
665
|
+
</article>
|
|
666
|
+
);
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
export const BlogCardFullWidthHorizontal = ({ article }: { article: BlogPost }) => {
|
|
670
|
+
const { authorName, authorHref, authorAvatarUrl, tagName, tagHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
671
|
+
|
|
672
|
+
return (
|
|
673
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset md:flex-row md:items-start">
|
|
674
|
+
<a href={href} className="shrink-0" tabIndex={-1}>
|
|
675
|
+
<PhotoWithFallback
|
|
676
|
+
item={article}
|
|
677
|
+
fallbackId={article.id}
|
|
678
|
+
alt={title}
|
|
679
|
+
className="h-60 w-full object-cover md:h-60 md:w-80"
|
|
680
|
+
/>
|
|
681
|
+
</a>
|
|
682
|
+
|
|
683
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
684
|
+
<div className="flex flex-col gap-2">
|
|
685
|
+
<Button href={tagHref} color="link-color">
|
|
686
|
+
{tagName}
|
|
687
|
+
</Button>
|
|
688
|
+
<div className="flex flex-col gap-2">
|
|
689
|
+
<Button href={href} color="link-gray" size="xl" className="text-xl font-semibold text-primary md:text-lg">
|
|
690
|
+
{title}
|
|
691
|
+
</Button>
|
|
692
|
+
|
|
693
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<div className="flex gap-2">
|
|
698
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
699
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
700
|
+
</a>
|
|
701
|
+
|
|
702
|
+
<div>
|
|
703
|
+
<p className="text-sm font-semibold">
|
|
704
|
+
<Button href={authorHref} color="link-gray" className="text-primary">
|
|
705
|
+
{authorName}
|
|
706
|
+
</Button>
|
|
707
|
+
</p>
|
|
708
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
709
|
+
</div>
|
|
710
|
+
</div>
|
|
711
|
+
</div>
|
|
712
|
+
</article>
|
|
713
|
+
);
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
export const BlogCardFullWidthHorizontalAlt = ({ article }: { article: BlogPost }) => {
|
|
717
|
+
const { authorName, authorHref, authorAvatarUrl, tagName, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
718
|
+
|
|
719
|
+
return (
|
|
720
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset md:flex-row md:items-start">
|
|
721
|
+
<a href={href} className="shrink-0" tabIndex={-1}>
|
|
722
|
+
<PhotoWithFallback
|
|
723
|
+
item={article}
|
|
724
|
+
fallbackId={article.id}
|
|
725
|
+
alt={title}
|
|
726
|
+
className="h-60 w-full object-cover md:h-60.5 md:w-80"
|
|
727
|
+
/>
|
|
728
|
+
</a>
|
|
729
|
+
|
|
730
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
731
|
+
<div className="flex flex-col gap-4">
|
|
732
|
+
<BadgeGroup addonText={tagName} size="md" theme="light" color="brand" className="pr-3" iconTrailing={null}>
|
|
733
|
+
Blog
|
|
734
|
+
</BadgeGroup>
|
|
735
|
+
<div className="flex flex-col gap-2">
|
|
736
|
+
<Button href={href} color="link-gray" size="xl" className="text-xl font-semibold text-primary md:text-lg">
|
|
737
|
+
{title}
|
|
738
|
+
</Button>
|
|
739
|
+
|
|
740
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
|
|
744
|
+
<div className="flex gap-2">
|
|
745
|
+
<a href={authorHref} tabIndex={-1} className="flex">
|
|
746
|
+
<Avatar focusable alt={authorName} src={authorAvatarUrl} size="md" />
|
|
747
|
+
</a>
|
|
748
|
+
|
|
749
|
+
<div>
|
|
750
|
+
<p className="text-sm font-semibold">
|
|
751
|
+
<Button href={authorHref} color="link-gray" className="text-primary">
|
|
752
|
+
{authorName}
|
|
753
|
+
</Button>
|
|
754
|
+
</p>
|
|
755
|
+
<time className="block text-sm text-tertiary">{publishedAt}</time>
|
|
756
|
+
</div>
|
|
757
|
+
</div>
|
|
758
|
+
</div>
|
|
759
|
+
</article>
|
|
760
|
+
);
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
export const BlogCardFullWidthHorizontalCompact = ({ article }: { article: BlogPost }) => {
|
|
764
|
+
const { authorName, authorHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
765
|
+
|
|
766
|
+
return (
|
|
767
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset md:flex-row md:items-start">
|
|
768
|
+
<a href={href} className="shrink-0" tabIndex={-1}>
|
|
769
|
+
<PhotoWithFallback
|
|
770
|
+
item={article}
|
|
771
|
+
fallbackId={article.id}
|
|
772
|
+
alt={title}
|
|
773
|
+
className="h-60 w-full object-cover md:h-60 md:w-80"
|
|
774
|
+
/>
|
|
775
|
+
</a>
|
|
776
|
+
|
|
777
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
778
|
+
<div className="flex flex-col gap-2">
|
|
779
|
+
<p className="text-sm font-semibold text-brand-secondary">
|
|
780
|
+
<Button href={authorHref} color="link-color">
|
|
781
|
+
{authorName}
|
|
782
|
+
</Button>{" "}
|
|
783
|
+
• <time>{publishedAt}</time>
|
|
784
|
+
</p>
|
|
785
|
+
<div className="flex flex-col gap-2">
|
|
786
|
+
<Button href={href} color="link-gray" size="xl" className="text-xl font-semibold text-primary md:text-lg">
|
|
787
|
+
{title}
|
|
788
|
+
</Button>
|
|
789
|
+
|
|
790
|
+
<p className="line-clamp-2 text-md text-tertiary">{summary}</p>
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
</article>
|
|
795
|
+
);
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
export const BlogCardFullWidthHorizontalMinimal = ({ article }: { article: BlogPost }) => {
|
|
799
|
+
const { authorName, authorHref, tagName, tagHref, href, title, publishedAt, summary } = getBlogPostData(article);
|
|
800
|
+
|
|
801
|
+
return (
|
|
802
|
+
<article className="flex flex-col overflow-hidden rounded-2xl ring-1 ring-secondary ring-inset md:flex-row md:items-start">
|
|
803
|
+
<div className="relative shrink-0">
|
|
804
|
+
<a href={href} className="w-full" tabIndex={-1}>
|
|
805
|
+
<PhotoWithFallback
|
|
806
|
+
item={article}
|
|
807
|
+
fallbackId={article.id}
|
|
808
|
+
alt={title}
|
|
809
|
+
className="h-60 w-full object-cover md:h-60 md:w-80"
|
|
810
|
+
/>
|
|
811
|
+
</a>
|
|
812
|
+
<div className="absolute inset-x-0 bottom-0 overflow-hidden bg-linear-to-b from-transparent to-black/40">
|
|
813
|
+
<div className="relative flex items-start justify-between bg-alpha-white/30 p-4 backdrop-blur-md before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-alpha-white/30 md:p-6">
|
|
814
|
+
<div>
|
|
815
|
+
<p className="text-sm font-semibold">
|
|
816
|
+
<Button href={authorHref} color="link-gray" className="text-white">
|
|
817
|
+
{authorName}
|
|
818
|
+
</Button>
|
|
819
|
+
</p>
|
|
820
|
+
<time className="block text-sm text-white">{publishedAt}</time>
|
|
821
|
+
</div>
|
|
822
|
+
<p className="text-sm font-semibold">
|
|
823
|
+
<Button href={tagHref} color="link-gray" className="text-white">
|
|
824
|
+
{tagName}
|
|
825
|
+
</Button>
|
|
826
|
+
</p>
|
|
827
|
+
</div>
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
|
|
831
|
+
<div className="flex flex-col gap-6 p-5 pb-6 md:p-6">
|
|
832
|
+
<div className="flex flex-col gap-2">
|
|
833
|
+
<div className="flex flex-col gap-2">
|
|
834
|
+
<Button href={href} color="link-gray" size="xl" className="text-xl font-semibold text-primary md:text-lg">
|
|
835
|
+
{title}
|
|
836
|
+
</Button>
|
|
837
|
+
|
|
838
|
+
<p className="line-clamp-2 text-md text-tertiary md:line-clamp-3">{summary}</p>
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
|
|
842
|
+
<Button href={href} color="link-color" size="lg" iconTrailing={ArrowUpRight}>
|
|
843
|
+
Read post
|
|
844
|
+
</Button>
|
|
845
|
+
</div>
|
|
846
|
+
</article>
|
|
847
|
+
);
|
|
848
|
+
};
|