@fullstackdatasolutions/articles 1.0.0 → 1.2.0
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/CHANGELOG.md +20 -0
- package/README.md +10 -8
- package/dist/index.cjs +69 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +69 -28
- package/dist/index.js.map +1 -1
- package/dist/nextjs.d.cts +8 -0
- package/dist/nextjs.d.ts +8 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +8 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/ArticleCard.tsx +5 -5
- package/src/ArticleDetailHero.tsx +54 -20
- package/src/ArticlesPage.tsx +1 -0
- package/src/Breadcrumb.tsx +4 -1
- package/src/RelatedArticlesSection.tsx +4 -1
- package/src/__tests__/ArticleDetailHero.test.tsx +57 -0
- package/src/articlesConfig.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2026-08-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`Breadcrumb` accepts `containerClassName`.** New optional prop, default `'max-w-7xl'` (unchanged from before), applied to the breadcrumb `<ol>` in place of the previously hardcoded value. Lets a consuming site align the breadcrumb bar's content width to its own reading column instead of the package's fixed default. Found on theroleplayersguild.com, where the breadcrumb's `max-w-7xl` sat visibly wider than the article body's `max-w-3xl` column beneath it.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **`ArticleDetailHero` byline never rendered the configured author avatar.** It always showed a generic user icon even when a single `authors[]` entry had a real `avatar` set (and even though `RelatedArticlesSection`/`ArticleCard` byline avatars worked correctly from the same data). Now resolves and renders the avatar image for the single-author case, matching the existing `ArticleCard` pattern; falls back to the icon when no avatar is set or multiple authors are configured, so existing output is unchanged unless a consumer already had `authors[].avatar` configured.
|
|
17
|
+
|
|
18
|
+
## [1.1.0] - 2026-08-09
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Configurable heading font family.** New `ArticlesTheme.headerFontFamily`, optional and falling back to inherit/nothing when unset - existing consumers who don't set it see byte-identical output. Threaded through `ArticleDetailHero` (new optional `config` prop), `RelatedArticlesSection`, and `ArticleCard` (both already accepted `config`) via inline `style={{ fontFamily: config?.theme?.headerFontFamily }}`, and into `ArticlesPage`'s `buildThemeVars` as `--articles-header-font-family`, consistent with the package's existing CSS-var theming pattern. Lets a site use a distinct display face for headings (e.g. a serif) separate from its body `fontFamily`, without hardcoding either into the package. Found auditing theroleplayersguild.com's article pages, which use a serif heading treatment the package couldn't previously express.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **`ArticleCard` hardcoded an indigo hover color.** The title link's `hover:text-indigo-600` was a stray leftover unrelated to any theme token and would visibly clash with any site not using an indigo-ish primary color. Replaced with `hover:text-primary`, matching the token already used elsewhere in the same component.
|
|
27
|
+
|
|
8
28
|
## [1.0.0] - 2026-08-05
|
|
9
29
|
|
|
10
30
|
### Breaking
|
package/README.md
CHANGED
|
@@ -194,6 +194,7 @@ export default async function Page({ params }: ArticlePageProps) {
|
|
|
194
194
|
article={article}
|
|
195
195
|
authors={authors}
|
|
196
196
|
categoryBasePath="/articles/category"
|
|
197
|
+
config={siteConfig}
|
|
197
198
|
/>
|
|
198
199
|
<ArticleViewTracker article={article} config={siteConfig} />
|
|
199
200
|
{siteConfig.showToc !== false && article.toc && article.toc.length > 0 && (
|
|
@@ -580,17 +581,18 @@ All theme values are applied as CSS custom properties on a wrapper `<div>`. Comp
|
|
|
580
581
|
|
|
581
582
|
```ts
|
|
582
583
|
theme: {
|
|
583
|
-
fontFamily:
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
584
|
+
fontFamily: "'Inter', sans-serif",
|
|
585
|
+
headerFontFamily: "'Cinzel', serif",
|
|
586
|
+
headerColor: '#111827',
|
|
587
|
+
textColor: '#6b7280',
|
|
588
|
+
backgroundColor: '#ffffff',
|
|
589
|
+
linkColor: '#4f46e5',
|
|
590
|
+
linkHoverColor: '#4338ca',
|
|
591
|
+
borderRadius: '0.5rem',
|
|
590
592
|
}
|
|
591
593
|
```
|
|
592
594
|
|
|
593
|
-
All fields are optional. Omitted fields fall back to your Tailwind theme tokens.
|
|
595
|
+
All fields are optional. Omitted fields fall back to your Tailwind theme tokens. `headerFontFamily` styles article titles and card/section headings (`ArticleDetailHero`, `RelatedArticlesSection`, `ArticleCard`) separately from body text; omit it to let headings inherit `fontFamily` like everything else.
|
|
594
596
|
|
|
595
597
|
---
|
|
596
598
|
|
package/dist/index.cjs
CHANGED
|
@@ -265,6 +265,7 @@ function getDisplayItems(items) {
|
|
|
265
265
|
function Breadcrumb({
|
|
266
266
|
items,
|
|
267
267
|
className = "",
|
|
268
|
+
containerClassName = "max-w-7xl",
|
|
268
269
|
showSchema = true,
|
|
269
270
|
separator = ">"
|
|
270
271
|
}) {
|
|
@@ -276,7 +277,7 @@ function Breadcrumb({
|
|
|
276
277
|
{
|
|
277
278
|
"aria-label": "Breadcrumb",
|
|
278
279
|
className: `border-b border-border bg-background/80 px-4 py-3 text-sm text-muted-foreground ${className}`,
|
|
279
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ol", { className:
|
|
280
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ol", { className: `mx-auto flex items-center gap-2 overflow-hidden ${containerClassName}`, children: displayItems.map((item, index) => {
|
|
280
281
|
const isLast = index === displayItems.length - 1;
|
|
281
282
|
const key = `${item.name}-${index}`;
|
|
282
283
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "flex min-w-0 items-center gap-2", children: [
|
|
@@ -650,6 +651,7 @@ function emitArticleEvent(handler, event) {
|
|
|
650
651
|
// src/ArticleCard.tsx
|
|
651
652
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
652
653
|
function ArticleCard({ article, config }) {
|
|
654
|
+
var _a;
|
|
653
655
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
|
|
654
656
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "p-0", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
655
657
|
import_image4.default,
|
|
@@ -663,14 +665,14 @@ function ArticleCard({ article, config }) {
|
|
|
663
665
|
) }),
|
|
664
666
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "p-6", children: [
|
|
665
667
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
|
|
666
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
667
|
-
|
|
668
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
669
|
+
"h3",
|
|
668
670
|
{
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
children: article.title
|
|
671
|
+
className: "text-lg font-semibold leading-none tracking-tight mb-2",
|
|
672
|
+
style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
|
|
673
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_link6.default, { href: `/articles/${article.slug}`, className: "hover:text-primary transition-colors", children: article.title })
|
|
672
674
|
}
|
|
673
|
-
)
|
|
675
|
+
),
|
|
674
676
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
|
|
675
677
|
article.authorSlug && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
676
678
|
import_link6.default,
|
|
@@ -993,6 +995,7 @@ function buildThemeVars(theme) {
|
|
|
993
995
|
if (!theme) return {};
|
|
994
996
|
const vars = {};
|
|
995
997
|
if (theme.fontFamily) vars["--articles-font-family"] = theme.fontFamily;
|
|
998
|
+
if (theme.headerFontFamily) vars["--articles-header-font-family"] = theme.headerFontFamily;
|
|
996
999
|
if (theme.headerColor) vars["--articles-header-color"] = theme.headerColor;
|
|
997
1000
|
if (theme.textColor) vars["--articles-text-color"] = theme.textColor;
|
|
998
1001
|
if (theme.backgroundColor) vars["--articles-bg-color"] = theme.backgroundColor;
|
|
@@ -1153,13 +1156,24 @@ var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
|
1153
1156
|
function toSlug(cat) {
|
|
1154
1157
|
return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
1155
1158
|
}
|
|
1159
|
+
function resolveAvatarUrl(author, config) {
|
|
1160
|
+
if (!author.avatar) return null;
|
|
1161
|
+
if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
|
|
1162
|
+
return author.avatar;
|
|
1163
|
+
}
|
|
1164
|
+
if (!(config == null ? void 0 : config.siteUrl)) return null;
|
|
1165
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1166
|
+
return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
|
|
1167
|
+
}
|
|
1156
1168
|
function ArticleDetailHero({
|
|
1157
1169
|
article,
|
|
1158
1170
|
categoryBasePath = "/articles/category",
|
|
1159
1171
|
showDate = false,
|
|
1160
1172
|
showAuthor = true,
|
|
1161
|
-
authors = []
|
|
1173
|
+
authors = [],
|
|
1174
|
+
config
|
|
1162
1175
|
}) {
|
|
1176
|
+
var _a;
|
|
1163
1177
|
const showConfiguredAuthors = showAuthor && authors.length > 0;
|
|
1164
1178
|
const showLegacyAuthor = showAuthor && authors.length === 0 && article.author.trim().length > 0;
|
|
1165
1179
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
@@ -1240,7 +1254,14 @@ function ArticleDetailHero({
|
|
|
1240
1254
|
)
|
|
1241
1255
|
}
|
|
1242
1256
|
),
|
|
1243
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1257
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1258
|
+
"h1",
|
|
1259
|
+
{
|
|
1260
|
+
className: "text-4xl md:text-5xl font-bold mb-4",
|
|
1261
|
+
style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
|
|
1262
|
+
children: article.title
|
|
1263
|
+
}
|
|
1264
|
+
),
|
|
1244
1265
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1245
1266
|
"div",
|
|
1246
1267
|
{
|
|
@@ -1261,24 +1282,36 @@ function ArticleDetailHero({
|
|
|
1261
1282
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.Clock, { className: "h-4 w-4" }),
|
|
1262
1283
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: article.readTime })
|
|
1263
1284
|
] }),
|
|
1264
|
-
showConfiguredAuthors &&
|
|
1265
|
-
|
|
1266
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1285
|
+
showConfiguredAuthors && (() => {
|
|
1286
|
+
const singleAvatarUrl = authors.length === 1 ? resolveAvatarUrl(authors[0], config) : null;
|
|
1287
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
1288
|
+
singleAvatarUrl ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1289
|
+
import_image5.default,
|
|
1290
|
+
{
|
|
1291
|
+
src: singleAvatarUrl,
|
|
1292
|
+
alt: "",
|
|
1293
|
+
width: 20,
|
|
1294
|
+
height: 20,
|
|
1295
|
+
className: "h-5 w-5 rounded-full object-cover"
|
|
1296
|
+
}
|
|
1297
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.User, { className: "h-4 w-4" }),
|
|
1298
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
|
|
1299
|
+
"By",
|
|
1300
|
+
" ",
|
|
1301
|
+
authors.map((author, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react4.Fragment, { children: [
|
|
1302
|
+
index > 0 && ", ",
|
|
1303
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1304
|
+
import_link8.default,
|
|
1305
|
+
{
|
|
1306
|
+
href: `/articles/authors/${author.slug}`,
|
|
1307
|
+
className: "font-medium text-white underline-offset-4 hover:underline",
|
|
1308
|
+
children: author.name
|
|
1309
|
+
}
|
|
1310
|
+
)
|
|
1311
|
+
] }, author.slug))
|
|
1312
|
+
] })
|
|
1313
|
+
] });
|
|
1314
|
+
})(),
|
|
1282
1315
|
showLegacyAuthor && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
1283
1316
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.User, { className: "h-4 w-4" }),
|
|
1284
1317
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
|
|
@@ -2006,9 +2039,17 @@ function RelatedArticlesSection({
|
|
|
2006
2039
|
fromSlug,
|
|
2007
2040
|
source = "category"
|
|
2008
2041
|
}) {
|
|
2042
|
+
var _a;
|
|
2009
2043
|
if (articles.length === 0) return null;
|
|
2010
2044
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("section", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Related articles", children: [
|
|
2011
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2045
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2046
|
+
"h2",
|
|
2047
|
+
{
|
|
2048
|
+
className: "text-xl font-semibold text-foreground mb-6",
|
|
2049
|
+
style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
|
|
2050
|
+
children: heading != null ? heading : `More in ${category}`
|
|
2051
|
+
}
|
|
2052
|
+
),
|
|
2012
2053
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: articles.map((article) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2013
2054
|
"div",
|
|
2014
2055
|
{
|