@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/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: "mx-auto flex max-w-7xl items-center gap-2 overflow-hidden", children: displayItems.map((item, index) => {
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("h3", { className: "text-lg font-semibold leading-none tracking-tight mb-2", children: /* @__PURE__ */ jsx8(
606
- Link6,
607
+ /* @__PURE__ */ jsx8(
608
+ "h3",
607
609
  {
608
- href: `/articles/${article.slug}`,
609
- className: "hover:text-indigo-600 transition-colors",
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("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
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 && /* @__PURE__ */ jsxs13("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1204
- /* @__PURE__ */ jsx13(User2, { className: "h-4 w-4" }),
1205
- /* @__PURE__ */ jsxs13("span", { children: [
1206
- "By",
1207
- " ",
1208
- authors.map((author, index) => /* @__PURE__ */ jsxs13(Fragment3, { children: [
1209
- index > 0 && ", ",
1210
- /* @__PURE__ */ jsx13(
1211
- Link8,
1212
- {
1213
- href: `/articles/authors/${author.slug}`,
1214
- className: "font-medium text-white underline-offset-4 hover:underline",
1215
- children: author.name
1216
- }
1217
- )
1218
- ] }, author.slug))
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("h2", { className: "text-xl font-semibold text-foreground mb-6", children: heading != null ? heading : `More in ${category}` }),
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
  {