@contentgrowth/content-widget 1.3.4 → 1.3.5

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.
@@ -4,12 +4,14 @@
4
4
  */
5
5
  import React, { useEffect, useState } from 'react';
6
6
  import { ContentGrowthClient } from '../core/client.js';
7
- import { formatDate, calculateReadingTime } from '../core/utils.js';
8
- export const ContentList = ({ apiKey, baseUrl, layout = 'cards', displayMode = 'comfortable', theme = 'light', pageSize = 12, tags = [], category, showPagination = true, linkPattern = '/articles/{uuid}', showTags = false, showAiSummary = true, summaryMaxLength, linkTarget, className = '' }) => {
7
+ import { FeaturedCard } from './FeaturedCard.js';
8
+ import { ContentCard } from './ContentCard.js';
9
+ export const ContentList = ({ apiKey, baseUrl, layout = 'cards', displayMode = 'comfortable', displayAs = 'default', theme = 'light', pageSize = 12, tags = [], category, showPagination = true, linkPattern = '/articles/{uuid}', showTags = false, showAiSummary = true, summaryMaxLength, linkTarget, className = '' }) => {
9
10
  const [articles, setArticles] = useState([]);
10
11
  const [currentPage, setCurrentPage] = useState(1);
11
12
  const [totalPages, setTotalPages] = useState(1);
12
13
  const [loading, setLoading] = useState(true);
14
+ const isFeaturedCardsMode = displayAs === 'featured-cards';
13
15
  useEffect(() => {
14
16
  const fetchArticles = async () => {
15
17
  setLoading(true);
@@ -73,27 +75,16 @@ export const ContentList = ({ apiKey, baseUrl, layout = 'cards', displayMode = '
73
75
  React.createElement("div", { className: "cg-empty-state" },
74
76
  React.createElement("p", null, "Loading..."))));
75
77
  }
76
- return (React.createElement("div", { className: `cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${className}`, "data-cg-widget": "list" }, articles.length === 0 ? (React.createElement("div", { className: "cg-empty-state" },
78
+ return (React.createElement("div", { className: `cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${isFeaturedCardsMode ? 'cg-featured-cards-list' : ''} ${className}`, "data-cg-widget": "list" }, articles.length === 0 ? (React.createElement("div", { className: "cg-empty-state" },
77
79
  React.createElement("p", null, "No articles found."))) : (React.createElement(React.Fragment, null,
78
- React.createElement("div", { className: `cg-articles-grid ${layout === 'cards' ? 'cg-grid' : 'cg-list'}` }, articles.map((article) => {
79
- const articleUrl = buildArticleUrl(article);
80
+ React.createElement("div", { className: `cg-articles-grid ${isFeaturedCardsMode ? 'cg-featured-cards-grid' : (layout === 'cards' ? 'cg-grid' : 'cg-list')}` }, articles.map((article) => {
80
81
  const articleTarget = buildLinkTarget(article);
81
- const readingTime = calculateReadingTime(article.wordCount);
82
- const publishedDate = formatDate(article.publishedAt);
83
- return (React.createElement("article", { key: article.uuid, className: "cg-article-card" },
84
- React.createElement("a", { href: articleUrl, target: articleTarget, className: "cg-card-link" },
85
- React.createElement("div", { className: "cg-card-content" },
86
- article.category && (React.createElement("div", { className: "cg-card-category" },
87
- React.createElement("span", { className: "cg-category-badge" }, article.category))),
88
- React.createElement("h2", { className: "cg-card-title" }, article.title),
89
- showAiSummary && article.summary && (React.createElement("p", { className: "cg-card-summary" }, truncateSummary(article.summary, summaryMaxLength))),
90
- React.createElement("div", { className: "cg-card-meta" },
91
- React.createElement("span", { className: "cg-meta-author" }, article.authorName),
92
- React.createElement("span", { className: "cg-meta-separator" }, "\u2022"),
93
- React.createElement("time", { className: "cg-meta-date", dateTime: new Date(article.publishedAt * 1000).toISOString() }, publishedDate),
94
- React.createElement("span", { className: "cg-meta-separator" }, "\u2022"),
95
- React.createElement("span", { className: "cg-meta-reading-time" }, readingTime)),
96
- showTags && article.tags && article.tags.length > 0 && (React.createElement("div", { className: "cg-card-tags" }, article.tags.map((tag) => (React.createElement("span", { key: tag, className: "cg-tag" }, tag)))))))));
82
+ // Featured Cards Mode - Use FeaturedCard component
83
+ if (isFeaturedCardsMode) {
84
+ return (React.createElement(FeaturedCard, { key: article.uuid, article: article, linkPattern: linkPattern, linkTarget: articleTarget, showCategory: true }));
85
+ }
86
+ // Default Card Mode - Use ContentCard component
87
+ return (React.createElement(ContentCard, { key: article.uuid, article: article, linkPattern: linkPattern, linkTarget: articleTarget, showSummary: showAiSummary, summaryMaxLength: summaryMaxLength, showTags: showTags, showCategory: true }));
97
88
  })),
98
89
  showPagination && totalPages > 1 && (React.createElement("div", { className: "cg-pagination" },
99
90
  React.createElement("button", { className: "cg-pagination-btn", onClick: () => setCurrentPage(p => Math.max(1, p - 1)), disabled: currentPage === 1 }, "Previous"),
@@ -1,6 +1,18 @@
1
1
  import React from 'react';
2
- import type { FeaturedContentProps } from '../types';
3
- interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton' | 'backUrl' | 'showAiSummary' | 'showTags'> {
2
+ import type { FeaturedContentProps, ArticleWithContent, Article } from '../types';
3
+ interface FeaturedCardProps extends Partial<Omit<FeaturedContentProps, 'showBackButton' | 'backUrl' | 'showAiSummary' | 'showTags'>> {
4
+ /**
5
+ * Pre-loaded article data (bypasses API fetch - used by ContentList)
6
+ */
7
+ article?: Article | ArticleWithContent;
8
+ /**
9
+ * Load specific article by slug (bypasses category/tags search)
10
+ */
11
+ slug?: string;
12
+ /**
13
+ * Load specific article by UUID (bypasses category/tags search)
14
+ */
15
+ uuid?: string;
4
16
  /**
5
17
  * URL pattern for the article link
6
18
  * Supports placeholders: {uuid}, {slug}, {category}
@@ -1 +1 @@
1
- {"version":3,"file":"FeaturedCard.d.ts","sourceRoot":"","sources":["../../src/react/FeaturedCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,UAAU,CAAC;AAEzE,UAAU,iBAAkB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,GAAG,SAAS,GAAG,eAAe,GAAG,UAAU,CAAC;IACvH;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAEnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAEzC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA8IpD,CAAC"}
1
+ {"version":3,"file":"FeaturedCard.d.ts","sourceRoot":"","sources":["../../src/react/FeaturedCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAElF,UAAU,iBAAkB,SAAQ,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,GAAG,SAAS,GAAG,eAAe,GAAG,UAAU,CAAC,CAAC;IAChI;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAAC;IAEvC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAEnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAEzC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA0KpD,CAAC"}
@@ -1,31 +1,54 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import { marked } from 'marked';
3
3
  import { ContentGrowthClient } from '../core/client';
4
- export const FeaturedCard = ({ apiKey, baseUrl, tags = [], category, excludeTags = [], showCategory = true, showReadingTime = false, showAuthor = false, ctaText: propCtaText, linkPattern = '/articles/{slug}', linkTarget, layout: propLayout, borderStyle = 'none', borderColor = '#e5e7eb', cardBackground = 'none', itemsBackground = '#f3f4f6', className = '' }) => {
5
- const [article, setArticle] = useState(null);
6
- const [loading, setLoading] = useState(true);
4
+ export const FeaturedCard = ({ apiKey, baseUrl, article: providedArticle, slug, uuid, tags = [], category, excludeTags = [], showCategory = true, showReadingTime = false, showAuthor = false, ctaText: propCtaText, linkPattern = '/articles/{slug}', linkTarget, layout: propLayout, borderStyle = 'none', borderColor = '#e5e7eb', cardBackground = 'none', itemsBackground = '#f3f4f6', className = '' }) => {
5
+ const [article, setArticle] = useState(providedArticle || null);
6
+ const [loading, setLoading] = useState(!providedArticle);
7
7
  const [error, setError] = useState(null);
8
8
  useEffect(() => {
9
+ // If article is provided, use it directly (no fetch needed)
10
+ if (providedArticle) {
11
+ setArticle(providedArticle);
12
+ setLoading(false);
13
+ return;
14
+ }
15
+ // Need API key to fetch
16
+ if (!apiKey) {
17
+ setLoading(false);
18
+ return;
19
+ }
9
20
  const fetchArticle = async () => {
10
21
  setLoading(true);
11
22
  try {
12
23
  const client = new ContentGrowthClient({ apiKey, baseUrl });
13
- const fetchedArticle = await client.getFeaturedArticle({
14
- tags,
15
- category,
16
- excludeTags
17
- });
24
+ let fetchedArticle;
25
+ if (uuid) {
26
+ // Mode 2: Load by UUID
27
+ fetchedArticle = await client.getArticle(uuid, { excludeTags });
28
+ }
29
+ else if (slug) {
30
+ // Mode 3: Load by slug
31
+ fetchedArticle = await client.getArticleBySlug(slug, { excludeTags });
32
+ }
33
+ else {
34
+ // Mode 4: Find featured article by category/tags (original behavior)
35
+ fetchedArticle = await client.getFeaturedArticle({
36
+ tags,
37
+ category,
38
+ excludeTags
39
+ });
40
+ }
18
41
  setArticle(fetchedArticle);
19
42
  }
20
43
  catch (err) {
21
- setError(err instanceof Error ? err.message : 'Failed to load featured article');
44
+ setError(err instanceof Error ? err.message : 'Failed to load article');
22
45
  }
23
46
  finally {
24
47
  setLoading(false);
25
48
  }
26
49
  };
27
50
  fetchArticle();
28
- }, [apiKey, baseUrl, category, JSON.stringify(tags), JSON.stringify(excludeTags)]);
51
+ }, [apiKey, baseUrl, providedArticle, uuid, slug, category, JSON.stringify(tags), JSON.stringify(excludeTags)]);
29
52
  // Generate article URL
30
53
  const getArticleUrl = (article) => {
31
54
  return linkPattern
@@ -48,10 +71,10 @@ export const FeaturedCard = ({ apiKey, baseUrl, tags = [], category, excludeTags
48
71
  return (React.createElement("div", { className: `cg-widget cg-error ${className}` }, error || 'No featured content found'));
49
72
  }
50
73
  const summaryHtml = getSummaryHtml(article);
51
- // Explicitly use any to access dynamic properties until types are fully updated
52
- const layout = propLayout || article.featuredSummaryLayout || 'standard';
74
+ const layout = propLayout || article.featuredSummaryLayout || 'vertical';
53
75
  const readingTime = Math.ceil(article.wordCount / 200);
54
76
  const borderClass = borderStyle !== 'none' ? `cg-border-${borderStyle}` : '';
77
+ const layoutClass = layout !== 'vertical' ? `cg-layout-${layout}` : '';
55
78
  // Use ctaText from prop, or from article data, or fallback to default
56
79
  const ctaText = propCtaText || article.featuredCtaText || 'Read full story';
57
80
  const customStyles = {};
@@ -61,7 +84,7 @@ export const FeaturedCard = ({ apiKey, baseUrl, tags = [], category, excludeTags
61
84
  customStyles['--cg-card-bg'] = cardBackground;
62
85
  if (itemsBackground !== '#f3f4f6')
63
86
  customStyles['--cg-items-bg'] = itemsBackground;
64
- return (React.createElement("a", { href: getArticleUrl(article), className: `cg-widget cg-featured-card ${className} ${layout !== 'standard' ? `cg-layout-${layout}` : ''} ${borderClass}`, style: Object.keys(customStyles).length > 0 ? customStyles : undefined, "data-cg-widget": "featured-card", target: linkTarget, rel: linkTarget === '_blank' ? 'noopener noreferrer' : undefined },
87
+ return (React.createElement("a", { href: getArticleUrl(article), className: `cg-widget cg-featured-card ${className} ${layoutClass} ${borderClass}`, style: Object.keys(customStyles).length > 0 ? customStyles : undefined, "data-cg-widget": "featured-card", target: linkTarget, rel: linkTarget === '_blank' ? 'noopener noreferrer' : undefined },
65
88
  React.createElement("article", { className: "cg-featured-card-inner" },
66
89
  showCategory && article.category && (React.createElement("div", { className: "cg-featured-card-category" },
67
90
  React.createElement("span", { className: "cg-category-badge" }, article.category))),
@@ -5,7 +5,9 @@ export { ContentList } from './ContentList.js';
5
5
  export { ContentViewer } from './ContentViewer.js';
6
6
  export { FeaturedContent } from './FeaturedContent.js';
7
7
  export { FeaturedCard } from './FeaturedCard.js';
8
+ export { ContentCard } from './ContentCard.js';
8
9
  export * from './hooks.js';
9
10
  export type { ReactContentListProps } from './ContentList.js';
10
11
  export type { ReactContentViewerProps } from './ContentViewer.js';
12
+ export type { ContentCardProps } from './ContentCard.js';
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,cAAc,YAAY,CAAC;AAE3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,cAAc,YAAY,CAAC;AAE3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -5,4 +5,5 @@ export { ContentList } from './ContentList.js';
5
5
  export { ContentViewer } from './ContentViewer.js';
6
6
  export { FeaturedContent } from './FeaturedContent.js';
7
7
  export { FeaturedCard } from './FeaturedCard.js';
8
+ export { ContentCard } from './ContentCard.js';
8
9
  export * from './hooks.js';
package/dist/styles.css CHANGED
@@ -720,10 +720,27 @@ a.cg-card {
720
720
  margin-bottom: 2rem;
721
721
  }
722
722
 
723
+ /* Featured Cards Grid for displayAs="featured-cards" */
724
+ .cg-articles-grid.cg-featured-cards-grid {
725
+ display: grid;
726
+ grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
727
+ gap: 2rem;
728
+ margin-bottom: 2rem;
729
+ }
730
+
731
+ .cg-articles-grid.cg-featured-cards-grid .cg-featured-card {
732
+ height: 100%;
733
+ }
734
+
723
735
  @media (max-width: 768px) {
724
736
  .cg-articles-grid.cg-grid {
725
737
  grid-template-columns: 1fr;
726
738
  }
739
+
740
+ .cg-articles-grid.cg-featured-cards-grid {
741
+ grid-template-columns: 1fr;
742
+ gap: 1.5rem;
743
+ }
727
744
  }
728
745
 
729
746
  /* Article Card */
@@ -13,6 +13,8 @@ export interface Article {
13
13
  publishedAt: number;
14
14
  summary: string | null;
15
15
  featuredSummary: string | null;
16
+ featuredSummaryLayout?: 'vertical' | 'horizontal';
17
+ featuredCtaText?: string;
16
18
  tags: string[];
17
19
  wordCount: number;
18
20
  }
@@ -126,6 +128,10 @@ export type DisplayMode = 'compact' | 'comfortable' | 'spacious';
126
128
  * Theme mode
127
129
  */
128
130
  export type Theme = 'light' | 'dark';
131
+ /**
132
+ * Display style for content list
133
+ */
134
+ export type DisplayAs = 'default' | 'featured-cards';
129
135
  /**
130
136
  * Component props for ContentList
131
137
  */
@@ -149,6 +155,13 @@ export interface ContentListProps {
149
155
  * @default 'comfortable'
150
156
  */
151
157
  displayMode?: DisplayMode;
158
+ /**
159
+ * Display style - how to render each article
160
+ * 'default' = standard card/row layout
161
+ * 'featured-cards' = FeaturedCard style with summary and CTA
162
+ * @default 'default'
163
+ */
164
+ displayAs?: DisplayAs;
152
165
  /**
153
166
  * Theme
154
167
  * @default 'light'
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, computed, watch } from 'vue';
3
+ import { ContentGrowthClient } from '../core/client';
4
+ import { formatDate, calculateReadingTime } from '../core/utils';
5
+ import type { Article, ArticleWithContent } from '../types';
6
+
7
+ const props = withDefaults(defineProps<{
8
+ // Pre-loaded article data (bypasses API fetch)
9
+ article?: Article | ArticleWithContent;
10
+ // Load specific article by slug
11
+ slug?: string;
12
+ // Load specific article by UUID
13
+ uuid?: string;
14
+ // API config (required if fetching)
15
+ apiKey?: string;
16
+ baseUrl?: string;
17
+ // Display options
18
+ linkPattern?: string;
19
+ linkTarget?: string;
20
+ showSummary?: boolean;
21
+ summaryMaxLength?: number;
22
+ showTags?: boolean;
23
+ showCategory?: boolean;
24
+ className?: string;
25
+ }>(), {
26
+ linkPattern: '/articles/{slug}',
27
+ showSummary: true,
28
+ showTags: false,
29
+ showCategory: true,
30
+ className: ''
31
+ });
32
+
33
+ const loadedArticle = ref<Article | ArticleWithContent | null>(props.article || null);
34
+ const loading = ref(!props.article && !!(props.apiKey && (props.slug || props.uuid)));
35
+ const error = ref<string | null>(null);
36
+
37
+ // Watch for provided article prop changes
38
+ watch(() => props.article, (newArticle) => {
39
+ if (newArticle) {
40
+ loadedArticle.value = newArticle;
41
+ loading.value = false;
42
+ }
43
+ }, { immediate: true });
44
+
45
+ // Generate article URL
46
+ const articleUrl = computed(() => {
47
+ if (!loadedArticle.value) return '#';
48
+ return props.linkPattern
49
+ .replace('{uuid}', loadedArticle.value.uuid || '')
50
+ .replace('{slug}', loadedArticle.value.slug || loadedArticle.value.uuid || '')
51
+ .replace('{category}', loadedArticle.value.category || 'uncategorized');
52
+ });
53
+
54
+ // Truncate summary text
55
+ const truncateSummary = (text: string | null, maxLength?: number): string => {
56
+ if (!text) return '';
57
+ if (!maxLength || text.length <= maxLength) return text;
58
+ return text.substring(0, maxLength).trim() + '...';
59
+ };
60
+
61
+ const readingTime = computed(() => {
62
+ if (!loadedArticle.value) return '';
63
+ return calculateReadingTime(loadedArticle.value.wordCount);
64
+ });
65
+
66
+ const publishedDate = computed(() => {
67
+ if (!loadedArticle.value) return '';
68
+ return formatDate(loadedArticle.value.publishedAt);
69
+ });
70
+
71
+ const publishedDateTime = computed(() => {
72
+ if (!loadedArticle.value) return '';
73
+ return new Date(loadedArticle.value.publishedAt * 1000).toISOString();
74
+ });
75
+
76
+ onMounted(async () => {
77
+ // If article is already provided, no need to fetch
78
+ if (props.article) {
79
+ loadedArticle.value = props.article;
80
+ loading.value = false;
81
+ return;
82
+ }
83
+
84
+ // Need API key and identifier to fetch
85
+ if (!props.apiKey || (!props.slug && !props.uuid)) {
86
+ loading.value = false;
87
+ return;
88
+ }
89
+
90
+ loading.value = true;
91
+ try {
92
+ const client = new ContentGrowthClient({
93
+ apiKey: props.apiKey,
94
+ baseUrl: props.baseUrl
95
+ });
96
+
97
+ if (props.uuid) {
98
+ loadedArticle.value = await client.getArticle(props.uuid);
99
+ } else if (props.slug) {
100
+ loadedArticle.value = await client.getArticleBySlug(props.slug);
101
+ }
102
+ } catch (err) {
103
+ error.value = err instanceof Error ? err.message : 'Failed to load article';
104
+ } finally {
105
+ loading.value = false;
106
+ }
107
+ });
108
+ </script>
109
+
110
+ <template>
111
+ <div v-if="loading" class="cg-widget cg-loading" :class="className">
112
+ <div class="cg-spinner"></div>
113
+ </div>
114
+
115
+ <div v-else-if="error" class="cg-widget cg-error" :class="className">
116
+ {{ error }}
117
+ </div>
118
+
119
+ <article v-else-if="loadedArticle" class="cg-article-card" :class="className">
120
+ <a :href="articleUrl" :target="linkTarget" class="cg-card-link">
121
+ <div class="cg-card-content">
122
+ <div v-if="showCategory && loadedArticle.category" class="cg-card-category">
123
+ <span class="cg-category-badge">{{ loadedArticle.category }}</span>
124
+ </div>
125
+
126
+ <h2 class="cg-card-title">{{ loadedArticle.title }}</h2>
127
+
128
+ <p v-if="showSummary && loadedArticle.summary" class="cg-card-summary">
129
+ {{ truncateSummary(loadedArticle.summary, summaryMaxLength) }}
130
+ </p>
131
+
132
+ <div class="cg-card-meta">
133
+ <span class="cg-meta-author">{{ loadedArticle.authorName }}</span>
134
+ <span class="cg-meta-separator">•</span>
135
+ <time class="cg-meta-date" :datetime="publishedDateTime">
136
+ {{ publishedDate }}
137
+ </time>
138
+ <span class="cg-meta-separator">•</span>
139
+ <span class="cg-meta-reading-time">{{ readingTime }}</span>
140
+ </div>
141
+
142
+ <div v-if="showTags && loadedArticle.tags && loadedArticle.tags.length > 0" class="cg-card-tags">
143
+ <span v-for="tag in loadedArticle.tags" :key="tag" class="cg-tag">{{ tag }}</span>
144
+ </div>
145
+ </div>
146
+ </a>
147
+ </article>
148
+ </template>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div
3
- :class="`cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${className}`"
3
+ :class="`cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${isFeaturedCardsMode ? 'cg-featured-cards-list' : ''} ${className}`"
4
4
  data-cg-widget="list"
5
5
  >
6
6
  <div v-if="loading" class="cg-empty-state">
@@ -10,36 +10,33 @@
10
10
  <p>No articles found.</p>
11
11
  </div>
12
12
  <template v-else>
13
- <div :class="`cg-articles-grid ${layout === 'cards' ? 'cg-grid' : 'cg-list'}`">
14
- <article v-for="article in articles" :key="article.uuid" class="cg-article-card">
15
- <a :href="buildArticleUrl(article)" :target="buildLinkTarget(article)" class="cg-card-link">
16
- <div class="cg-card-content">
17
- <div v-if="article.category" class="cg-card-category">
18
- <span class="cg-category-badge">{{ article.category }}</span>
19
- </div>
20
-
21
- <h2 class="cg-card-title">{{ article.title }}</h2>
22
-
23
- <p v-if="showAiSummary && article.summary" class="cg-card-summary">
24
- {{ truncateSummary(article.summary, summaryMaxLength) }}
25
- </p>
26
-
27
- <div class="cg-card-meta">
28
- <span class="cg-meta-author">{{ article.authorName }}</span>
29
- <span class="cg-meta-separator">•</span>
30
- <time class="cg-meta-date" :datetime="new Date(article.publishedAt * 1000).toISOString()">
31
- {{ formatDate(article.publishedAt) }}
32
- </time>
33
- <span class="cg-meta-separator">•</span>
34
- <span class="cg-meta-reading-time">{{ calculateReadingTime(article.wordCount) }}</span>
35
- </div>
36
-
37
- <div v-if="showTags && article.tags && article.tags.length > 0" class="cg-card-tags">
38
- <span v-for="tag in article.tags" :key="tag" class="cg-tag">{{ tag }}</span>
39
- </div>
40
- </div>
41
- </a>
42
- </article>
13
+ <div :class="`cg-articles-grid ${isFeaturedCardsMode ? 'cg-featured-cards-grid' : (layout === 'cards' ? 'cg-grid' : 'cg-list')}`">
14
+ <!-- Featured Cards Mode -->
15
+ <template v-if="isFeaturedCardsMode">
16
+ <FeaturedCard
17
+ v-for="article in articles"
18
+ :key="article.uuid"
19
+ :article="article"
20
+ :linkPattern="linkPattern"
21
+ :linkTarget="buildLinkTarget(article)"
22
+ :showCategory="true"
23
+ />
24
+ </template>
25
+
26
+ <!-- Default Card Mode - Use ContentCard component -->
27
+ <template v-else>
28
+ <ContentCard
29
+ v-for="article in articles"
30
+ :key="article.uuid"
31
+ :article="article"
32
+ :linkPattern="linkPattern"
33
+ :linkTarget="buildLinkTarget(article)"
34
+ :showSummary="showAiSummary"
35
+ :summaryMaxLength="summaryMaxLength"
36
+ :showTags="showTags"
37
+ :showCategory="true"
38
+ />
39
+ </template>
43
40
  </div>
44
41
 
45
42
  <div v-if="showPagination && totalPages > 1" class="cg-pagination">
@@ -68,9 +65,10 @@
68
65
  </template>
69
66
 
70
67
  <script setup lang="ts">
71
- import { ref, watch, onMounted } from 'vue';
68
+ import { ref, watch, onMounted, computed } from 'vue';
72
69
  import { ContentGrowthClient } from '../core/client.js';
73
- import { formatDate, calculateReadingTime } from '../core/utils.js';
70
+ import FeaturedCard from './FeaturedCard.vue';
71
+ import ContentCard from './ContentCard.vue';
74
72
  import type { ContentListProps, Article } from '../types/index.js';
75
73
 
76
74
  export interface VueContentListProps extends Omit<ContentListProps, 'class'> {
@@ -80,6 +78,7 @@ export interface VueContentListProps extends Omit<ContentListProps, 'class'> {
80
78
  const props = withDefaults(defineProps<VueContentListProps>(), {
81
79
  layout: 'cards',
82
80
  displayMode: 'comfortable',
81
+ displayAs: 'default',
83
82
  theme: 'light',
84
83
  pageSize: 12,
85
84
  tags: () => [],
@@ -95,6 +94,8 @@ const currentPage = ref(1);
95
94
  const totalPages = ref(1);
96
95
  const loading = ref(true);
97
96
 
97
+ const isFeaturedCardsMode = computed(() => props.displayAs === 'featured-cards');
98
+
98
99
  const fetchArticles = async () => {
99
100
  loading.value = true;
100
101
  try {
@@ -171,3 +172,4 @@ watch(
171
172
  }
172
173
  );
173
174
  </script>
175
+