@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/dist/index.d.cts
CHANGED
|
@@ -232,6 +232,14 @@ type ArticlesSection = 'hero' | 'search' | 'featured' | 'latest' | 'categories'
|
|
|
232
232
|
interface ArticlesTheme {
|
|
233
233
|
/** Font family for the articles section. Example: `"'Inter', sans-serif"` */
|
|
234
234
|
fontFamily?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Font family for headings only (article title, card titles, section
|
|
237
|
+
* headings) - falls back to `fontFamily` when omitted. Lets a site use a
|
|
238
|
+
* distinct display face for headings (e.g. a serif) while keeping a
|
|
239
|
+
* separate body font, without hardcoding either into the package.
|
|
240
|
+
* Example: `"'Cinzel', serif"`
|
|
241
|
+
*/
|
|
242
|
+
headerFontFamily?: string;
|
|
235
243
|
/** Color for article card titles and section headings. Example: `'#111827'` */
|
|
236
244
|
headerColor?: string;
|
|
237
245
|
/** Color for body and excerpt text. Example: `'#6b7280'` */
|
|
@@ -453,8 +461,10 @@ type ArticleDetailHeroProps = Readonly<{
|
|
|
453
461
|
showDate?: boolean;
|
|
454
462
|
showAuthor?: boolean;
|
|
455
463
|
authors?: AuthorProfile[];
|
|
464
|
+
/** Optional - when `config.theme.headerFontFamily` is set, applies it to the title. */
|
|
465
|
+
config?: ArticlesConfig;
|
|
456
466
|
}>;
|
|
457
|
-
declare function ArticleDetailHero({ article, categoryBasePath, showDate, showAuthor, authors, }: ArticleDetailHeroProps): react_jsx_runtime.JSX.Element;
|
|
467
|
+
declare function ArticleDetailHero({ article, categoryBasePath, showDate, showAuthor, authors, config, }: ArticleDetailHeroProps): react_jsx_runtime.JSX.Element;
|
|
458
468
|
|
|
459
469
|
type ArticleSearchBarProps = Readonly<{
|
|
460
470
|
value: string;
|
|
@@ -592,10 +602,12 @@ declare function AuthorDetailHero({ author, articleCount }: AuthorDetailHeroProp
|
|
|
592
602
|
type BreadcrumbProps = Readonly<{
|
|
593
603
|
items: BreadcrumbItem[];
|
|
594
604
|
className?: string;
|
|
605
|
+
/** Max-width utility class applied to the breadcrumb list. Default: 'max-w-7xl'. */
|
|
606
|
+
containerClassName?: string;
|
|
595
607
|
showSchema?: boolean;
|
|
596
608
|
separator?: string;
|
|
597
609
|
}>;
|
|
598
|
-
declare function Breadcrumb({ items, className, showSchema, separator, }: BreadcrumbProps): react_jsx_runtime.JSX.Element | null;
|
|
610
|
+
declare function Breadcrumb({ items, className, containerClassName, showSchema, separator, }: BreadcrumbProps): react_jsx_runtime.JSX.Element | null;
|
|
599
611
|
declare function BreadcrumbSchema({ items }: Readonly<{
|
|
600
612
|
items: BreadcrumbItem[];
|
|
601
613
|
}>): react_jsx_runtime.JSX.Element;
|
package/dist/index.d.ts
CHANGED
|
@@ -232,6 +232,14 @@ type ArticlesSection = 'hero' | 'search' | 'featured' | 'latest' | 'categories'
|
|
|
232
232
|
interface ArticlesTheme {
|
|
233
233
|
/** Font family for the articles section. Example: `"'Inter', sans-serif"` */
|
|
234
234
|
fontFamily?: string;
|
|
235
|
+
/**
|
|
236
|
+
* Font family for headings only (article title, card titles, section
|
|
237
|
+
* headings) - falls back to `fontFamily` when omitted. Lets a site use a
|
|
238
|
+
* distinct display face for headings (e.g. a serif) while keeping a
|
|
239
|
+
* separate body font, without hardcoding either into the package.
|
|
240
|
+
* Example: `"'Cinzel', serif"`
|
|
241
|
+
*/
|
|
242
|
+
headerFontFamily?: string;
|
|
235
243
|
/** Color for article card titles and section headings. Example: `'#111827'` */
|
|
236
244
|
headerColor?: string;
|
|
237
245
|
/** Color for body and excerpt text. Example: `'#6b7280'` */
|
|
@@ -453,8 +461,10 @@ type ArticleDetailHeroProps = Readonly<{
|
|
|
453
461
|
showDate?: boolean;
|
|
454
462
|
showAuthor?: boolean;
|
|
455
463
|
authors?: AuthorProfile[];
|
|
464
|
+
/** Optional - when `config.theme.headerFontFamily` is set, applies it to the title. */
|
|
465
|
+
config?: ArticlesConfig;
|
|
456
466
|
}>;
|
|
457
|
-
declare function ArticleDetailHero({ article, categoryBasePath, showDate, showAuthor, authors, }: ArticleDetailHeroProps): react_jsx_runtime.JSX.Element;
|
|
467
|
+
declare function ArticleDetailHero({ article, categoryBasePath, showDate, showAuthor, authors, config, }: ArticleDetailHeroProps): react_jsx_runtime.JSX.Element;
|
|
458
468
|
|
|
459
469
|
type ArticleSearchBarProps = Readonly<{
|
|
460
470
|
value: string;
|
|
@@ -592,10 +602,12 @@ declare function AuthorDetailHero({ author, articleCount }: AuthorDetailHeroProp
|
|
|
592
602
|
type BreadcrumbProps = Readonly<{
|
|
593
603
|
items: BreadcrumbItem[];
|
|
594
604
|
className?: string;
|
|
605
|
+
/** Max-width utility class applied to the breadcrumb list. Default: 'max-w-7xl'. */
|
|
606
|
+
containerClassName?: string;
|
|
595
607
|
showSchema?: boolean;
|
|
596
608
|
separator?: string;
|
|
597
609
|
}>;
|
|
598
|
-
declare function Breadcrumb({ items, className, showSchema, separator, }: BreadcrumbProps): react_jsx_runtime.JSX.Element | null;
|
|
610
|
+
declare function Breadcrumb({ items, className, containerClassName, showSchema, separator, }: BreadcrumbProps): react_jsx_runtime.JSX.Element | null;
|
|
599
611
|
declare function BreadcrumbSchema({ items }: Readonly<{
|
|
600
612
|
items: BreadcrumbItem[];
|
|
601
613
|
}>): react_jsx_runtime.JSX.Element;
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,7 @@ function getDisplayItems(items) {
|
|
|
194
194
|
function Breadcrumb({
|
|
195
195
|
items,
|
|
196
196
|
className = "",
|
|
197
|
+
containerClassName = "max-w-7xl",
|
|
197
198
|
showSchema = true,
|
|
198
199
|
separator = ">"
|
|
199
200
|
}) {
|
|
@@ -205,7 +206,7 @@ function Breadcrumb({
|
|
|
205
206
|
{
|
|
206
207
|
"aria-label": "Breadcrumb",
|
|
207
208
|
className: `border-b border-border bg-background/80 px-4 py-3 text-sm text-muted-foreground ${className}`,
|
|
208
|
-
children: /* @__PURE__ */ jsx2("ol", { className:
|
|
209
|
+
children: /* @__PURE__ */ jsx2("ol", { className: `mx-auto flex items-center gap-2 overflow-hidden ${containerClassName}`, children: displayItems.map((item, index) => {
|
|
209
210
|
const isLast = index === displayItems.length - 1;
|
|
210
211
|
const key = `${item.name}-${index}`;
|
|
211
212
|
return /* @__PURE__ */ jsxs2("li", { className: "flex min-w-0 items-center gap-2", children: [
|
|
@@ -589,6 +590,7 @@ function emitArticleEvent(handler, event) {
|
|
|
589
590
|
// src/ArticleCard.tsx
|
|
590
591
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
591
592
|
function ArticleCard({ article, config }) {
|
|
593
|
+
var _a;
|
|
592
594
|
return /* @__PURE__ */ jsxs8("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
|
|
593
595
|
/* @__PURE__ */ jsx8("div", { className: "p-0", children: /* @__PURE__ */ jsx8(
|
|
594
596
|
Image4,
|
|
@@ -602,14 +604,14 @@ function ArticleCard({ article, config }) {
|
|
|
602
604
|
) }),
|
|
603
605
|
/* @__PURE__ */ jsxs8("div", { className: "p-6", children: [
|
|
604
606
|
/* @__PURE__ */ jsx8("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ jsx8("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
|
|
605
|
-
/* @__PURE__ */ jsx8(
|
|
606
|
-
|
|
607
|
+
/* @__PURE__ */ jsx8(
|
|
608
|
+
"h3",
|
|
607
609
|
{
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
children: article.title
|
|
610
|
+
className: "text-lg font-semibold leading-none tracking-tight mb-2",
|
|
611
|
+
style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
|
|
612
|
+
children: /* @__PURE__ */ jsx8(Link6, { href: `/articles/${article.slug}`, className: "hover:text-primary transition-colors", children: article.title })
|
|
611
613
|
}
|
|
612
|
-
)
|
|
614
|
+
),
|
|
613
615
|
/* @__PURE__ */ jsx8("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
|
|
614
616
|
article.authorSlug && /* @__PURE__ */ jsxs8(
|
|
615
617
|
Link6,
|
|
@@ -932,6 +934,7 @@ function buildThemeVars(theme) {
|
|
|
932
934
|
if (!theme) return {};
|
|
933
935
|
const vars = {};
|
|
934
936
|
if (theme.fontFamily) vars["--articles-font-family"] = theme.fontFamily;
|
|
937
|
+
if (theme.headerFontFamily) vars["--articles-header-font-family"] = theme.headerFontFamily;
|
|
935
938
|
if (theme.headerColor) vars["--articles-header-color"] = theme.headerColor;
|
|
936
939
|
if (theme.textColor) vars["--articles-text-color"] = theme.textColor;
|
|
937
940
|
if (theme.backgroundColor) vars["--articles-bg-color"] = theme.backgroundColor;
|
|
@@ -1092,13 +1095,24 @@ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
|
1092
1095
|
function toSlug(cat) {
|
|
1093
1096
|
return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
1094
1097
|
}
|
|
1098
|
+
function resolveAvatarUrl(author, config) {
|
|
1099
|
+
if (!author.avatar) return null;
|
|
1100
|
+
if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
|
|
1101
|
+
return author.avatar;
|
|
1102
|
+
}
|
|
1103
|
+
if (!(config == null ? void 0 : config.siteUrl)) return null;
|
|
1104
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1105
|
+
return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
|
|
1106
|
+
}
|
|
1095
1107
|
function ArticleDetailHero({
|
|
1096
1108
|
article,
|
|
1097
1109
|
categoryBasePath = "/articles/category",
|
|
1098
1110
|
showDate = false,
|
|
1099
1111
|
showAuthor = true,
|
|
1100
|
-
authors = []
|
|
1112
|
+
authors = [],
|
|
1113
|
+
config
|
|
1101
1114
|
}) {
|
|
1115
|
+
var _a;
|
|
1102
1116
|
const showConfiguredAuthors = showAuthor && authors.length > 0;
|
|
1103
1117
|
const showLegacyAuthor = showAuthor && authors.length === 0 && article.author.trim().length > 0;
|
|
1104
1118
|
return /* @__PURE__ */ jsxs13(
|
|
@@ -1179,7 +1193,14 @@ function ArticleDetailHero({
|
|
|
1179
1193
|
)
|
|
1180
1194
|
}
|
|
1181
1195
|
),
|
|
1182
|
-
/* @__PURE__ */ jsx13(
|
|
1196
|
+
/* @__PURE__ */ jsx13(
|
|
1197
|
+
"h1",
|
|
1198
|
+
{
|
|
1199
|
+
className: "text-4xl md:text-5xl font-bold mb-4",
|
|
1200
|
+
style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
|
|
1201
|
+
children: article.title
|
|
1202
|
+
}
|
|
1203
|
+
),
|
|
1183
1204
|
/* @__PURE__ */ jsxs13(
|
|
1184
1205
|
"div",
|
|
1185
1206
|
{
|
|
@@ -1200,24 +1221,36 @@ function ArticleDetailHero({
|
|
|
1200
1221
|
/* @__PURE__ */ jsx13(Clock3, { className: "h-4 w-4" }),
|
|
1201
1222
|
/* @__PURE__ */ jsx13("span", { children: article.readTime })
|
|
1202
1223
|
] }),
|
|
1203
|
-
showConfiguredAuthors &&
|
|
1204
|
-
|
|
1205
|
-
/* @__PURE__ */ jsxs13("
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1224
|
+
showConfiguredAuthors && (() => {
|
|
1225
|
+
const singleAvatarUrl = authors.length === 1 ? resolveAvatarUrl(authors[0], config) : null;
|
|
1226
|
+
return /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
1227
|
+
singleAvatarUrl ? /* @__PURE__ */ jsx13(
|
|
1228
|
+
Image5,
|
|
1229
|
+
{
|
|
1230
|
+
src: singleAvatarUrl,
|
|
1231
|
+
alt: "",
|
|
1232
|
+
width: 20,
|
|
1233
|
+
height: 20,
|
|
1234
|
+
className: "h-5 w-5 rounded-full object-cover"
|
|
1235
|
+
}
|
|
1236
|
+
) : /* @__PURE__ */ jsx13(User2, { className: "h-4 w-4" }),
|
|
1237
|
+
/* @__PURE__ */ jsxs13("span", { children: [
|
|
1238
|
+
"By",
|
|
1239
|
+
" ",
|
|
1240
|
+
authors.map((author, index) => /* @__PURE__ */ jsxs13(Fragment3, { children: [
|
|
1241
|
+
index > 0 && ", ",
|
|
1242
|
+
/* @__PURE__ */ jsx13(
|
|
1243
|
+
Link8,
|
|
1244
|
+
{
|
|
1245
|
+
href: `/articles/authors/${author.slug}`,
|
|
1246
|
+
className: "font-medium text-white underline-offset-4 hover:underline",
|
|
1247
|
+
children: author.name
|
|
1248
|
+
}
|
|
1249
|
+
)
|
|
1250
|
+
] }, author.slug))
|
|
1251
|
+
] })
|
|
1252
|
+
] });
|
|
1253
|
+
})(),
|
|
1221
1254
|
showLegacyAuthor && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
1222
1255
|
/* @__PURE__ */ jsx13(User2, { className: "h-4 w-4" }),
|
|
1223
1256
|
/* @__PURE__ */ jsxs13("span", { children: [
|
|
@@ -1945,9 +1978,17 @@ function RelatedArticlesSection({
|
|
|
1945
1978
|
fromSlug,
|
|
1946
1979
|
source = "category"
|
|
1947
1980
|
}) {
|
|
1981
|
+
var _a;
|
|
1948
1982
|
if (articles.length === 0) return null;
|
|
1949
1983
|
return /* @__PURE__ */ jsxs20("section", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Related articles", children: [
|
|
1950
|
-
/* @__PURE__ */ jsx21(
|
|
1984
|
+
/* @__PURE__ */ jsx21(
|
|
1985
|
+
"h2",
|
|
1986
|
+
{
|
|
1987
|
+
className: "text-xl font-semibold text-foreground mb-6",
|
|
1988
|
+
style: { fontFamily: (_a = config == null ? void 0 : config.theme) == null ? void 0 : _a.headerFontFamily },
|
|
1989
|
+
children: heading != null ? heading : `More in ${category}`
|
|
1990
|
+
}
|
|
1991
|
+
),
|
|
1951
1992
|
/* @__PURE__ */ jsx21("div", { className: "grid gap-6 sm:grid-cols-2 lg:grid-cols-3", children: articles.map((article) => /* @__PURE__ */ jsx21(
|
|
1952
1993
|
"div",
|
|
1953
1994
|
{
|