wpheadless-lib 1.0.0 → 1.0.2

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.
@@ -223,12 +223,22 @@ a:hover {
223
223
  display: inline-block;
224
224
  padding: 0.25rem 0.75rem;
225
225
  background: var(--primary);
226
- color: white;
226
+ color: #ffffff;
227
227
  font-size: 0.75rem;
228
228
  font-weight: 600;
229
229
  border-radius: 9999px;
230
230
  text-transform: uppercase;
231
231
  letter-spacing: 0.05em;
232
+ transition: var(--transition);
233
+ }
234
+
235
+ .badge:hover,
236
+ .badge:focus-visible,
237
+ a.badge,
238
+ a.badge:hover,
239
+ a.badge:focus-visible {
240
+ color: #ffffff !important;
241
+ background: var(--primary-hover);
232
242
  }
233
243
 
234
244
  /* Utilities */
@@ -1,11 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
3
  import styles from "./index.module.css";
4
+ function truncateLabel(label, max = 30) {
5
+ const trimmed = label.trim();
6
+ return trimmed.length > max ? `${trimmed.slice(0, max)}...` : trimmed;
7
+ }
4
8
  export function Breadcrumbs({ items }) {
5
9
  if (!items.length)
6
10
  return null;
7
11
  return (_jsx("nav", { "aria-label": "Breadcrumb", className: styles.nav, children: _jsx("ol", { className: styles.list, children: items.map((item, idx) => {
8
12
  const isLast = idx === items.length - 1;
9
- return (_jsxs("li", { className: styles.item, children: [item.href && !isLast ? (_jsx(Link, { href: item.href, className: styles.link, children: item.label })) : (_jsx("span", { className: styles.current, "aria-current": isLast ? "page" : undefined, children: item.label })), idx < items.length - 1 && (_jsx("span", { className: styles.separator, children: "/" }))] }, `${item.label}-${idx}`));
13
+ const display = truncateLabel(item.label, 30);
14
+ return (_jsxs("li", { className: styles.item, children: [item.href && !isLast ? (_jsx(Link, { href: item.href, className: styles.link, title: item.label, children: display })) : (_jsx("span", { className: styles.current, "aria-current": isLast ? "page" : undefined, title: item.label, children: display })), idx < items.length - 1 && (_jsx("span", { className: styles.separator, children: "/" }))] }, `${item.label}-${idx}`));
10
15
  }) }) }));
11
16
  }
@@ -4,6 +4,10 @@ import Link from "next/link";
4
4
  import { getFeaturedMedia } from "../../utils";
5
5
  import { buildCategoryUrl, buildPostUrl, formatPostDate, resolveCategorySlug, } from "./index.utils";
6
6
  import styles from "./index.module.css";
7
+ function truncateText(text, max = 20) {
8
+ const normalized = text.trim();
9
+ return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
10
+ }
7
11
  export default function PostCard({ post, categorySlug, locale = "en", dateLocale = "en-US", }) {
8
12
  const featuredMedia = getFeaturedMedia(post);
9
13
  const imageUrl = featuredMedia?.source_url;
@@ -14,9 +18,9 @@ export default function PostCard({ post, categorySlug, locale = "en", dateLocale
14
18
  categorySlug: resolvedCategory,
15
19
  locale,
16
20
  });
17
- return (_jsxs("article", { className: `${styles.card} card`, children: [categories.length > 0 && (_jsx("div", { className: styles.badges, children: categories.map((cat) => (_jsx(Link, { href: buildCategoryUrl(cat.slug, locale), className: `badge ${styles.badge}`, children: cat.name }, cat.id ?? cat.slug))) })), _jsxs(Link, { href: postUrl, className: styles.postLink, children: [imageUrl && (_jsx("div", { className: styles.imageWrapper, children: _jsx(Image, { src: imageUrl, alt: featuredMedia?.alt_text || post.title.rendered, fill: true, style: { objectFit: "cover" }, sizes: "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" }) })), _jsxs("div", { className: styles.body, children: [_jsx("h2", { dangerouslySetInnerHTML: {
21
+ return (_jsxs("article", { className: `${styles.card} card`, children: [categories.length > 0 && (_jsx("div", { className: styles.badges, children: categories.map((cat) => (_jsx(Link, { href: buildCategoryUrl(cat.slug, locale), className: `badge ${styles.badge}`, title: cat.name, children: truncateText(cat.name, 20) }, cat.id ?? cat.slug))) })), _jsxs(Link, { href: postUrl, className: styles.postLink, children: [imageUrl && (_jsx("div", { className: styles.imageWrapper, children: _jsx(Image, { src: imageUrl, alt: featuredMedia?.alt_text || post.title.rendered, fill: true, style: { objectFit: "cover" }, sizes: "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" }) })), _jsxs("div", { className: styles.body, children: [_jsx("h2", { className: styles.title, dangerouslySetInnerHTML: {
18
22
  __html: post.title.rendered,
19
- }, className: styles.title }), _jsx("div", { dangerouslySetInnerHTML: {
23
+ } }), _jsx("div", { dangerouslySetInnerHTML: {
20
24
  __html: post.excerpt.rendered,
21
25
  }, className: styles.excerpt }), _jsx("time", { dateTime: post.date, className: styles.date, children: formatPostDate(post.date, dateLocale) })] })] })] }));
22
26
  }
@@ -3,17 +3,23 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  height: 100%;
6
+ overflow: hidden;
6
7
  }
7
8
 
8
9
  .badges {
10
+ position: absolute;
11
+ top: 12px;
12
+ left: 12px;
13
+ right: 12px;
9
14
  display: flex;
10
15
  gap: 0.5rem;
11
16
  flex-wrap: wrap;
12
- padding: var(--spacing-sm) var(--spacing-md) 0;
17
+ z-index: 2;
13
18
  }
14
19
 
15
20
  .badge {
16
21
  text-decoration: none;
22
+ box-shadow: 0 6px 16px var(--shadow);
17
23
  }
18
24
 
19
25
  .postLink {
@@ -21,6 +27,8 @@
21
27
  color: inherit;
22
28
  display: block;
23
29
  height: 100%;
30
+ position: relative;
31
+ z-index: 1;
24
32
  }
25
33
 
26
34
  .imageWrapper {
@@ -33,7 +41,7 @@
33
41
  padding: var(--spacing-sm) var(--spacing-md) var(--spacing-md);
34
42
  display: flex;
35
43
  flex-direction: column;
36
- gap: var(--spacing-sm);
44
+ gap: var(--spacing-xs);
37
45
  align-items: flex-start;
38
46
  }
39
47
 
@@ -42,6 +50,7 @@
42
50
  font-weight: 700;
43
51
  line-height: 1.3;
44
52
  color: var(--foreground);
53
+ margin-top: 0;
45
54
  }
46
55
 
47
56
  .excerpt {
@@ -0,0 +1,19 @@
1
+ type MenuLink = {
2
+ id: number;
3
+ href: string;
4
+ label: string;
5
+ target?: string;
6
+ };
7
+ type HeaderLabels = {
8
+ navAria: string;
9
+ openMenu: string;
10
+ closeMenu: string;
11
+ };
12
+ type HeaderClientProps = {
13
+ menuLinks: MenuLink[];
14
+ siteName: string;
15
+ homeHref: string;
16
+ labels: HeaderLabels;
17
+ };
18
+ export default function HeaderClient({ menuLinks, siteName, homeHref, labels, }: HeaderClientProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { useEffect, useState } from "react";
5
+ import styles from "./index.module.css";
6
+ export default function HeaderClient({ menuLinks, siteName, homeHref, labels, }) {
7
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
8
+ const [isMobileView, setIsMobileView] = useState(false);
9
+ useEffect(() => {
10
+ const handleResize = () => {
11
+ const isMobile = window.innerWidth < 900;
12
+ setIsMobileView(isMobile);
13
+ if (!isMobile) {
14
+ setIsMenuOpen(false);
15
+ }
16
+ };
17
+ handleResize();
18
+ window.addEventListener("resize", handleResize);
19
+ return () => window.removeEventListener("resize", handleResize);
20
+ }, []);
21
+ const toggleMenu = () => setIsMenuOpen((prev) => !prev);
22
+ const handleLinkClick = () => setIsMenuOpen(false);
23
+ const menuLabel = isMenuOpen ? labels.closeMenu : labels.openMenu;
24
+ return (_jsxs("div", { className: styles.inner, children: [_jsx(Link, { href: homeHref, className: styles.logo, children: siteName }), menuLinks.length > 0 && (_jsxs("div", { className: styles.navWrapper, children: [_jsxs("button", { type: "button", className: styles.menuButton, "aria-expanded": isMenuOpen, "aria-controls": "primary-navigation", "aria-label": menuLabel, onClick: toggleMenu, children: [_jsxs("span", { className: styles.menuIcon, "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), _jsx("span", { className: styles.menuText, children: menuLabel })] }), _jsx("nav", { id: "primary-navigation", "aria-label": labels.navAria, className: `${styles.nav} ${isMenuOpen ? styles.navOpen : ""}`, "aria-hidden": !isMenuOpen && isMobileView, hidden: !isMenuOpen && isMobileView, children: _jsx("ul", { className: styles.list, children: menuLinks.map((item) => (_jsx("li", { children: _jsx(Link, { href: item.href, className: "header-link", onClick: handleLinkClick, target: item.target || undefined, rel: item.target === "_blank"
25
+ ? "noopener noreferrer"
26
+ : undefined, children: item.label }) }, item.id))) }) })] }))] }));
27
+ }
@@ -1,7 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import Link from "next/link";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
3
2
  import { createMenuEndpoints, createAuth } from "wpjsapi-lib";
4
3
  import { convertWpUrl, getTranslator } from "../../../utils";
4
+ import HeaderClient from "./HeaderClient";
5
5
  import styles from "./index.module.css";
6
6
  export const dynamic = "force-static";
7
7
  export const revalidate = 3600;
@@ -32,5 +32,16 @@ export default async function Header({ menuId, wpApiUrl, wpUsername, wpAppPasswo
32
32
  catch (e) {
33
33
  console.error("Error obteniendo menú:", e);
34
34
  }
35
- return (_jsx("header", { className: styles.header, children: _jsx("div", { className: "container", children: _jsxs("div", { className: styles.inner, children: [_jsx(Link, { href: homeHref, className: styles.logo, children: siteName }), _jsx("div", { className: styles.navWrapper, children: menuItems.length > 0 && (_jsx("nav", { "aria-label": t("header.navAria"), className: styles.nav, children: _jsx("ul", { className: styles.list, children: menuItems.map((item) => (_jsx("li", { children: _jsx(Link, { href: convertWpUrl(item.url, wpApiUrl), className: "header-link", children: item.title.rendered }) }, item.id))) }) })) })] }) }) }));
35
+ const menuLinks = menuItems.map((item) => ({
36
+ id: item.id,
37
+ href: convertWpUrl(item.url, wpApiUrl),
38
+ label: item.title.rendered,
39
+ target: item.target || undefined,
40
+ }));
41
+ const labels = {
42
+ navAria: t("header.navAria"),
43
+ openMenu: t("header.openMenu"),
44
+ closeMenu: t("header.closeMenu"),
45
+ };
46
+ return (_jsx("header", { className: styles.header, children: _jsx("div", { className: "container", children: _jsx(HeaderClient, { menuLinks: menuLinks, siteName: siteName, homeHref: homeHref, labels: labels }) }) }));
36
47
  }
@@ -1,4 +1,5 @@
1
1
  .header {
2
+ --header-height: 78px;
2
3
  position: sticky;
3
4
  top: 0;
4
5
  z-index: 100;
@@ -14,6 +15,8 @@
14
15
  justify-content: space-between;
15
16
  padding: 1.25rem 0;
16
17
  gap: 3rem;
18
+ position: relative;
19
+ min-height: var(--header-height);
17
20
  }
18
21
 
19
22
  .logo {
@@ -25,13 +28,6 @@
25
28
  text-decoration: none;
26
29
  }
27
30
 
28
- .navWrapper {
29
- display: flex;
30
- align-items: center;
31
- gap: 2rem;
32
- flex: 1;
33
- }
34
-
35
31
  .nav {
36
32
  flex: 1;
37
33
  display: flex;
@@ -46,3 +42,127 @@
46
42
  margin: 0;
47
43
  padding: 0;
48
44
  }
45
+
46
+ .navWrapper {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 1.5rem;
50
+ flex: 1;
51
+ justify-content: flex-end;
52
+ }
53
+
54
+ .menuButton {
55
+ display: none;
56
+ align-items: center;
57
+ gap: 0.5rem;
58
+ padding: 0.6rem 0.75rem;
59
+ border-radius: 10px;
60
+ border: 1px solid var(--border);
61
+ background: var(--background);
62
+ color: var(--foreground);
63
+ font-weight: 600;
64
+ cursor: pointer;
65
+ transition: var(--transition);
66
+ position: relative;
67
+ z-index: 120;
68
+ }
69
+
70
+ .menuButton:hover {
71
+ border-color: var(--primary);
72
+ color: var(--primary);
73
+ }
74
+
75
+ .menuIcon {
76
+ position: relative;
77
+ width: 20px;
78
+ height: 14px;
79
+ display: inline-block;
80
+ }
81
+
82
+ .menuIcon span {
83
+ position: absolute;
84
+ left: 0;
85
+ width: 100%;
86
+ height: 2px;
87
+ background: currentColor;
88
+ border-radius: 999px;
89
+ transition: transform 0.3s ease, opacity 0.3s ease;
90
+ }
91
+
92
+ .menuIcon span:nth-child(1) {
93
+ top: 0;
94
+ }
95
+
96
+ .menuIcon span:nth-child(2) {
97
+ top: 6px;
98
+ }
99
+
100
+ .menuIcon span:nth-child(3) {
101
+ top: 12px;
102
+ }
103
+
104
+ .menuButton[aria-expanded="true"] .menuIcon span:nth-child(1) {
105
+ transform: translateY(6px) rotate(45deg);
106
+ }
107
+
108
+ .menuButton[aria-expanded="true"] .menuIcon span:nth-child(2) {
109
+ opacity: 0;
110
+ }
111
+
112
+ .menuButton[aria-expanded="true"] .menuIcon span:nth-child(3) {
113
+ transform: translateY(-6px) rotate(-45deg);
114
+ }
115
+
116
+ .menuText {
117
+ position: absolute;
118
+ width: 1px;
119
+ height: 1px;
120
+ padding: 0;
121
+ margin: -1px;
122
+ overflow: hidden;
123
+ clip: rect(0, 0, 0, 0);
124
+ white-space: nowrap;
125
+ border: 0;
126
+ }
127
+
128
+ @media (max-width: 900px) {
129
+ .inner {
130
+ gap: 1.25rem;
131
+ }
132
+
133
+ .nav {
134
+ position: fixed;
135
+ top: var(--header-height);
136
+ left: 0;
137
+ right: 0;
138
+ bottom: 0;
139
+ height: calc(100vh - var(--header-height));
140
+ padding: 1.5rem 1.25rem 2.5rem;
141
+ background: var(--card-bg);
142
+ display: block;
143
+ overflow: hidden;
144
+ opacity: 0;
145
+ pointer-events: none;
146
+ transform: translateY(-8px);
147
+ transition: opacity 0.25s ease, transform 0.25s ease;
148
+ z-index: 90;
149
+ }
150
+
151
+ .navOpen {
152
+ opacity: 1;
153
+ pointer-events: auto;
154
+ overflow: auto;
155
+ transform: translateY(0);
156
+ }
157
+
158
+ .list {
159
+ flex-direction: column;
160
+ align-items: flex-start;
161
+ gap: 1rem;
162
+ padding: 1rem 0 1.25rem;
163
+ }
164
+
165
+ .menuButton {
166
+ display: inline-flex;
167
+ }
168
+ }
@@ -7,6 +7,8 @@ const defaultMessages = {
7
7
  },
8
8
  header: {
9
9
  navAria: "Main navigation",
10
+ openMenu: "Open menu",
11
+ closeMenu: "Close menu",
10
12
  },
11
13
  site: {
12
14
  title: "Site title",
@@ -61,6 +63,8 @@ const defaultMessages = {
61
63
  },
62
64
  header: {
63
65
  navAria: "Navegación principal",
66
+ openMenu: "Abrir menú",
67
+ closeMenu: "Cerrar menú",
64
68
  },
65
69
  site: {
66
70
  title: "Título del sitio",
@@ -54,6 +54,7 @@ type BreadcrumbItem = {
54
54
  export declare function buildBreadcrumbSchema(items: BreadcrumbItem[]): {
55
55
  "@context": string;
56
56
  "@type": string;
57
+ "@id": string;
57
58
  itemListElement: {
58
59
  item?: string;
59
60
  "@type": string;
package/dist/utils/seo.js CHANGED
@@ -78,6 +78,7 @@ export function buildBreadcrumbSchema(items) {
78
78
  return {
79
79
  "@context": "https://schema.org",
80
80
  "@type": "BreadcrumbList",
81
+ "@id": `${items[items.length - 1].item}#breadcrumb`,
81
82
  itemListElement: items.map((it, idx) => ({
82
83
  "@type": "ListItem",
83
84
  position: idx + 1,
@@ -5,8 +5,9 @@ type CategoryListViewProps = {
5
5
  posts: WPPost[];
6
6
  totalPages: number;
7
7
  baseUrl?: string;
8
+ homeHref?: string;
8
9
  locale?: Locale;
9
10
  dateLocale?: string;
10
11
  };
11
- export declare function CategoryListView({ category, posts, totalPages, baseUrl, locale, dateLocale, }: CategoryListViewProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function CategoryListView({ category, posts, totalPages, baseUrl, homeHref, locale, dateLocale, }: CategoryListViewProps): import("react/jsx-runtime").JSX.Element;
12
13
  export {};
@@ -5,13 +5,13 @@ import styles from "./index.module.css";
5
5
  import { getCountLabel, getEmptyLabel, getPaginatorBase } from "./index.utils";
6
6
  import { Breadcrumbs } from "../../components";
7
7
  import { getTranslator } from "../../utils";
8
- export function CategoryListView({ category, posts, totalPages, baseUrl, locale, dateLocale = "en-US", }) {
8
+ export function CategoryListView({ category, posts, totalPages, baseUrl, homeHref = "/", locale, dateLocale = "en-US", }) {
9
9
  const paginatorBase = getPaginatorBase(category, baseUrl);
10
10
  const countLabel = getCountLabel(category, locale);
11
11
  const emptyLabel = getEmptyLabel(locale);
12
12
  const t = getTranslator(locale);
13
13
  return (_jsxs("div", { className: `container ${styles.container}`, children: [_jsxs("header", { className: styles.header, children: [_jsx(Breadcrumbs, { items: [
14
- { label: "Home", href: baseUrl || "/" },
14
+ { label: "Home", href: homeHref },
15
15
  { label: category.name },
16
16
  ] }), _jsx("h1", { className: styles.title, children: category.name }), category.description && (_jsx("div", { className: styles.description, dangerouslySetInnerHTML: { __html: category.description } })), countLabel && _jsx("p", { className: styles.count, children: countLabel })] }), posts.length === 0 ? (_jsx("p", { className: styles.empty, children: emptyLabel })) : (_jsxs("section", { "aria-label": t("home.title"), className: styles.gridSection, children: [_jsx("div", { className: styles.grid, children: posts.map((post) => (_jsx(PostCard, { post: post, categorySlug: category.slug, locale: locale, dateLocale: dateLocale }, post.id))) }), _jsx(Paginator, { currentPage: 1, totalPages: totalPages, baseUrl: paginatorBase, locale: locale })] }))] }));
17
17
  }
@@ -14,5 +14,5 @@ export function CategoryPaginationView({ category, posts, currentPage, totalPage
14
14
  { label: "Home", href: paginatorBase || "/" },
15
15
  { label: category.name },
16
16
  { label: pageLabel },
17
- ] }), _jsx("h1", { className: styles.title, children: category.name }), category.description && (_jsx("p", { className: styles.description, dangerouslySetInnerHTML: { __html: category.description } })), _jsx("p", { className: styles.pageLabel, children: pageLabel })] }), posts.length === 0 ? (_jsx("p", { className: styles.empty, children: emptyLabel })) : (_jsxs("section", { "aria-label": t("category.pageLabel", { page: currentPage }), className: styles.gridSection, children: [_jsx("div", { className: styles.grid, children: posts.map((post) => (_jsx(PostCard, { post: post, categorySlug: category.slug, locale: locale, dateLocale: dateLocale }, post.id))) }), _jsx(Paginator, { currentPage: currentPage, totalPages: totalPages, baseUrl: paginatorBase, locale: locale })] }))] }));
17
+ ] }), _jsx("h1", { className: styles.title, children: category.name }), _jsx("p", { className: styles.pageLabel, children: pageLabel })] }), posts.length === 0 ? (_jsx("p", { className: styles.empty, children: emptyLabel })) : (_jsxs("section", { "aria-label": t("category.pageLabel", { page: currentPage }), className: styles.gridSection, children: [_jsx("div", { className: styles.grid, children: posts.map((post) => (_jsx(PostCard, { post: post, categorySlug: category.slug, locale: locale, dateLocale: dateLocale }, post.id))) }), _jsx(Paginator, { currentPage: currentPage, totalPages: totalPages, baseUrl: paginatorBase, locale: locale })] }))] }));
18
18
  }
@@ -5,6 +5,10 @@ import Paginator from "../../components/Paginator";
5
5
  import PostCard from "../../components/PostCard";
6
6
  import styles from "./index.module.css";
7
7
  import { buildFeaturedViewModel, getHomeCopy } from "./index.utils";
8
+ function truncateLabel(label, max = 20) {
9
+ const trimmed = label.trim();
10
+ return trimmed.length > max ? `${trimmed.slice(0, max)}...` : trimmed;
11
+ }
8
12
  export function HomeView({ featuredPost, regularPosts, error, totalPages, baseUrl = "", locale, dateLocale = "en-US", }) {
9
13
  const copy = getHomeCopy(locale);
10
14
  const featuredModel = featuredPost
@@ -13,7 +17,7 @@ export function HomeView({ featuredPost, regularPosts, error, totalPages, baseUr
13
17
  return (_jsx("div", { className: `container ${styles.container}`, children: error ? (_jsxs("div", { className: styles.errorBox, children: [_jsxs("p", { children: [copy.errorTitle, ": ", error] }), _jsx("p", { className: styles.errorHint, children: copy.errorHint })] })) : !featuredModel && regularPosts.length === 0 ? (_jsx("p", { className: styles.empty, children: copy.empty })) : (_jsxs(_Fragment, { children: [featuredModel && (_jsxs("article", { className: `card ${styles.featured} ${featuredModel.image
14
18
  ? styles.featuredWithImage
15
19
  : styles.featuredNoImage}`, children: [featuredModel.image && (_jsx(Link, { href: featuredModel.postUrl, className: styles.featuredImageLink, children: _jsx(Image, { src: featuredModel.image.src, alt: featuredModel.image.alt, fill: true, className: styles.featuredImage, priority: true, sizes: "(max-width: 768px) 100vw, 50vw" }) })), _jsxs("div", { className: styles.featuredBody, children: [featuredModel.badgeLabel &&
16
- (featuredModel.badgeUrl ? (_jsx(Link, { href: featuredModel.badgeUrl, className: `${styles.featuredBadge} badge`, children: featuredModel.badgeLabel })) : (_jsx("span", { className: `${styles.featuredBadge} badge`, children: featuredModel.badgeLabel }))), _jsx(Link, { href: featuredModel.postUrl, children: _jsx("h2", { dangerouslySetInnerHTML: {
20
+ (featuredModel.badgeUrl ? (_jsx(Link, { href: featuredModel.badgeUrl, className: `${styles.featuredBadge} badge`, title: featuredModel.badgeLabel, children: truncateLabel(featuredModel.badgeLabel) })) : (_jsx("span", { className: `${styles.featuredBadge} badge`, title: featuredModel.badgeLabel, children: truncateLabel(featuredModel.badgeLabel) }))), _jsx(Link, { href: featuredModel.postUrl, children: _jsx("h2", { dangerouslySetInnerHTML: {
17
21
  __html: featuredModel.titleHtml,
18
22
  }, className: styles.featuredTitle }) }), _jsx("div", { dangerouslySetInnerHTML: {
19
23
  __html: featuredModel.excerptHtml,
@@ -24,7 +24,6 @@
24
24
  .featured {
25
25
  margin-bottom: var(--spacing-lg);
26
26
  display: grid;
27
- gap: var(--spacing-lg);
28
27
  }
29
28
 
30
29
  .featuredWithImage {
@@ -60,6 +59,7 @@
60
59
  .featuredTitle {
61
60
  margin-bottom: var(--spacing-sm);
62
61
  font-size: 2rem;
62
+ margin-top: 0;
63
63
  }
64
64
 
65
65
  .featuredExcerpt {
@@ -5,6 +5,10 @@ import { getTranslator } from "../../utils";
5
5
  import { Breadcrumbs } from "../../components";
6
6
  import { buildCategoryUrl, formatPublishedDate, getAuthor, getFeaturedImage, getPrimaryCategory, } from "./index.utils";
7
7
  import { stripTags } from "../../utils";
8
+ function truncateLabel(label, max = 20) {
9
+ const trimmed = label.trim();
10
+ return trimmed.length > max ? `${trimmed.slice(0, max)}...` : trimmed;
11
+ }
8
12
  export function PostView({ post, dateLocale = "en-US", categoryBasePath = "/", locale, }) {
9
13
  const t = getTranslator(locale);
10
14
  const author = getAuthor(post);
@@ -22,5 +26,5 @@ export function PostView({ post, dateLocale = "en-US", categoryBasePath = "/", l
22
26
  { label: "Home", href: categoryBasePath },
23
27
  { label: stripTags(post.title.rendered) || post.title.rendered },
24
28
  ];
25
- return (_jsx("div", { className: `container ${styles.container}`, children: _jsxs("article", { className: styles.article, children: [_jsx(Breadcrumbs, { items: breadcrumbs }), _jsxs("header", { className: styles.postHeader, children: [primaryCategory && (_jsx("div", { className: styles.badgeWrapper, children: categoryUrl ? (_jsx("a", { href: categoryUrl, className: "badge", children: primaryCategory.name })) : (_jsx("span", { className: "badge", children: primaryCategory.name })) })), _jsx("h1", { dangerouslySetInnerHTML: { __html: post.title.rendered }, className: styles.title }), _jsxs("div", { className: styles.meta, children: [author && (_jsx("span", { className: styles.authorLabel, children: t("post.authorPrefix", { name: author.name }) })), _jsx("span", { children: "\u2022" }), _jsx("time", { dateTime: post.date, children: publishedDate })] })] }), featuredImage && (_jsx("figure", { className: styles.figure, children: _jsx(Image, { src: featuredImage.src, alt: featuredImage.alt, width: featuredImage.width, height: featuredImage.height, className: styles.image, priority: true, sizes: "(max-width: 900px) 100vw, 900px" }) })), _jsx("div", { dangerouslySetInnerHTML: { __html: post.content.rendered }, className: `post-content ${styles.content}` }), author && author.description && (_jsxs("aside", { className: `card ${styles.authorCard}`, children: [_jsx("h2", { className: styles.authorTitle, children: t("post.aboutAuthor") }), _jsxs("address", { className: styles.authorAddress, children: [_jsx("strong", { className: styles.authorName, children: author.name }), _jsx("p", { className: styles.authorBio, children: author.description })] })] }))] }) }));
29
+ return (_jsx("div", { className: `container ${styles.container}`, children: _jsxs("article", { className: styles.article, children: [_jsx(Breadcrumbs, { items: breadcrumbs }), _jsx("header", { className: styles.postHeader, children: _jsx("h1", { dangerouslySetInnerHTML: { __html: post.title.rendered }, className: styles.title }) }), featuredImage && (_jsxs("figure", { className: styles.figure, children: [primaryCategory && (_jsx("div", { className: styles.badgeOverlay, children: categoryUrl ? (_jsx("a", { href: categoryUrl, className: "badge", title: primaryCategory.name, children: truncateLabel(primaryCategory.name, 20) })) : (_jsx("span", { className: "badge", title: primaryCategory.name, children: truncateLabel(primaryCategory.name, 20) })) })), _jsx(Image, { src: featuredImage.src, alt: featuredImage.alt, width: featuredImage.width, height: featuredImage.height, className: styles.image, priority: true, sizes: "(max-width: 900px) 100vw, 900px" })] })), _jsxs("div", { className: styles.meta, children: [author && (_jsx("span", { className: styles.authorLabel, children: t("post.authorPrefix", { name: author.name }) })), _jsx("span", { children: "\u2022" }), _jsx("time", { dateTime: post.date, children: publishedDate })] }), _jsx("div", { dangerouslySetInnerHTML: { __html: post.content.rendered }, className: `post-content ${styles.content}` }), author && author.description && (_jsxs("aside", { className: `card ${styles.authorCard}`, children: [_jsx("h2", { className: styles.authorTitle, children: t("post.aboutAuthor") }), _jsxs("address", { className: styles.authorAddress, children: [_jsx("strong", { className: styles.authorName, children: author.name }), _jsx("p", { className: styles.authorBio, children: author.description })] })] }))] }) }));
26
30
  }
@@ -1,5 +1,5 @@
1
1
  .container {
2
- padding-top: var(--spacing-xl);
2
+ padding-top: var(--spacing-sm);
3
3
  padding-bottom: var(--spacing-xl);
4
4
  }
5
5
 
@@ -8,25 +8,36 @@
8
8
  margin: 0 auto;
9
9
  }
10
10
 
11
- .badgeWrapper {
12
- margin-bottom: var(--spacing-sm);
11
+ .badgeOverlay {
12
+ position: absolute;
13
+ bottom: 25px;
14
+ left: 15px;
15
+ z-index: 2;
16
+ box-shadow: 0 6px 16px var(--shadow);
13
17
  }
14
18
 
15
19
  .title {
16
20
  font-size: clamp(2rem, 5vw, 3rem);
17
21
  font-weight: 800;
18
22
  line-height: 1.2;
19
- margin-bottom: var(--spacing-md);
20
23
  color: var(--foreground);
24
+ margin-bottom: var(--spacing-sm);
21
25
  }
22
26
 
23
27
  .meta {
24
28
  display: flex;
25
29
  align-items: center;
26
30
  gap: var(--spacing-sm);
27
- margin-bottom: var(--spacing-lg);
28
- color: var(--foreground-light);
29
- font-size: 0.9375rem;
31
+ margin: 0 0 var(--spacing-sm);
32
+ padding: 0.7rem 0.9rem;
33
+ background: rgba(0, 0, 0, 0.06);
34
+ border: 1px solid var(--border);
35
+ border-radius: 10px;
36
+ color: var(--foreground);
37
+ font-size: 0.8rem;
38
+ width: fit-content;
39
+ max-width: 100%;
40
+ flex-wrap: wrap;
30
41
  }
31
42
 
32
43
  .authorLabel {
@@ -34,7 +45,8 @@
34
45
  }
35
46
 
36
47
  .figure {
37
- margin-bottom: var(--spacing-lg);
48
+ margin-bottom: var(--spacing-xs);
49
+ position: relative;
38
50
  }
39
51
 
40
52
  .image {
@@ -51,8 +63,20 @@
51
63
  }
52
64
 
53
65
  .authorCard {
54
- margin-top: var(--spacing-xl);
55
- padding: var(--spacing-lg);
66
+ margin-top: var(--spacing-lg);
67
+ margin-left: auto;
68
+ margin-right: auto;
69
+ padding: var(--spacing-md) var(--spacing-lg);
70
+ max-width: 720px;
71
+ background: rgba(255, 255, 255, 0.03);
72
+ border: 1px solid var(--border);
73
+ box-shadow: 0 6px 18px var(--shadow);
74
+ transition: none;
75
+ }
76
+
77
+ .authorCard:hover {
78
+ box-shadow: 0 6px 18px var(--shadow);
79
+ transform: none;
56
80
  }
57
81
 
58
82
  .authorAddress {
@@ -60,7 +84,7 @@
60
84
  }
61
85
 
62
86
  .authorTitle {
63
- font-size: 1.25rem;
87
+ font-size: 1.1rem;
64
88
  font-weight: 700;
65
89
  margin-top: 0;
66
90
  margin-bottom: var(--spacing-md);
@@ -70,11 +94,12 @@
70
94
  .authorName {
71
95
  display: block;
72
96
  margin-bottom: var(--spacing-xs);
73
- font-size: 1.125rem;
97
+ font-size: 1rem;
74
98
  color: var(--foreground);
75
99
  }
76
100
 
77
101
  .authorBio {
78
102
  color: var(--foreground-light);
79
103
  line-height: 1.6;
104
+ font-size: 0.95rem;
80
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wpheadless-lib",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "wpjsapi-lib": "3.0.0"
28
+ "wpjsapi-lib": "3.1.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20",