@content-streamline/nextjs 0.0.3 → 0.0.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.
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import type { Post } from '../types/index.js';
3
+ export interface LatestPostsProps {
4
+ posts: Post[];
5
+ /**
6
+ * Number of posts to display (default: 3)
7
+ */
8
+ count?: number;
9
+ /**
10
+ * Base URL for post links (e.g., '/blog' or '/posts')
11
+ * Post URL will be: {baseUrl}/{postId}/{slug}
12
+ */
13
+ baseUrl?: string;
14
+ /**
15
+ * Platform filter for posts (default: 'blog')
16
+ * Valid values: 'x', 'linkedin', 'blog', 'facebook', 'instagram', 'other'
17
+ */
18
+ platform?: string;
19
+ /**
20
+ * Language to use for post translations (default: 'en')
21
+ */
22
+ language?: string;
23
+ /**
24
+ * Custom className for the container
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Custom className for post cards
29
+ */
30
+ cardClassName?: string;
31
+ /**
32
+ * Show post date (default: true)
33
+ */
34
+ showDate?: boolean;
35
+ /**
36
+ * Show post excerpt/description (default: true)
37
+ */
38
+ showExcerpt?: boolean;
39
+ /**
40
+ * Show post images (default: true)
41
+ */
42
+ showImages?: boolean;
43
+ /**
44
+ * Custom render function for post title
45
+ */
46
+ renderTitle?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
47
+ /**
48
+ * Custom render function for post excerpt
49
+ */
50
+ renderExcerpt?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
51
+ }
52
+ export declare function LatestPosts({ posts, count, baseUrl, platform, language, className, cardClassName, showDate, showExcerpt, showImages, renderTitle, renderExcerpt, }: LatestPostsProps): import("react/jsx-runtime").JSX.Element;
53
+ //# sourceMappingURL=LatestPosts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LatestPosts.d.ts","sourceRoot":"","sources":["../../src/components/LatestPosts.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IACzF;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5F;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAS,EACT,OAAkB,EAClB,QAAiB,EACjB,QAAe,EACf,SAAc,EACd,aAAkB,EAClB,QAAe,EACf,WAAkB,EAClB,UAAiB,EACjB,WAAW,EACX,aAAa,GACd,EAAE,gBAAgB,2CAuFlB"}
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ export function LatestPosts({ posts, count = 3, baseUrl = '/posts', platform = 'blog', language = 'en', className = '', cardClassName = '', showDate = true, showExcerpt = true, showImages = true, renderTitle, renderExcerpt, }) {
4
+ // Filter posts by platform if specified
5
+ const filteredPosts = platform
6
+ ? posts.filter(post => post.platform.toLowerCase() === platform.toLowerCase())
7
+ : posts;
8
+ // Take only the first N posts
9
+ const latestPosts = filteredPosts.slice(0, count);
10
+ if (latestPosts.length === 0) {
11
+ return (_jsx("div", { className: `content-streamline-empty ${className}`, children: _jsx("p", { children: "No posts found." }) }));
12
+ }
13
+ return (_jsx("div", { className: `content-streamline-latest-posts ${className}`, children: latestPosts.map((post, index) => {
14
+ const translation = post.post_translations.find((t) => t.language === language) ||
15
+ post.post_translations[0];
16
+ if (!translation || !translation.slug)
17
+ return null;
18
+ const featuredImage = post.images.find((img) => img.ordering === 0) || post.images[0];
19
+ const date = new Date(post.created_at);
20
+ const isFirst = index === 0;
21
+ return (_jsxs("article", { className: `content-streamline-latest-card ${isFirst ? 'content-streamline-latest-card--featured' : ''} ${cardClassName}`, children: [showImages && featuredImage && (_jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-latest-image-link", children: _jsx("div", { className: "content-streamline-latest-image", children: _jsx("img", { src: featuredImage.url, alt: featuredImage.alt || translation.title, loading: isFirst ? 'eager' : 'lazy' }) }) })), _jsxs("div", { className: "content-streamline-latest-content", children: [showDate && (_jsx("time", { className: "content-streamline-latest-date", dateTime: post.created_at, children: date.toLocaleDateString('en-US', {
22
+ year: 'numeric',
23
+ month: 'long',
24
+ day: 'numeric',
25
+ }) })), _jsx("h2", { className: "content-streamline-latest-title", children: _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, children: renderTitle ? renderTitle(post, translation) : translation.title }) }), showExcerpt && translation.meta_description && (_jsx("p", { className: "content-streamline-latest-excerpt", children: renderExcerpt
26
+ ? renderExcerpt(post, translation)
27
+ : translation.meta_description })), _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-latest-link", children: "Read more \u2192" })] })] }, post.id));
28
+ }) }));
29
+ }
@@ -14,6 +14,11 @@ export interface PostsListProps {
14
14
  * For best performance, also pass platform to getAllPosts() when fetching.
15
15
  */
16
16
  platform?: string;
17
+ /**
18
+ * Number of posts to skip from the beginning (default: 0)
19
+ * Useful when using LatestPosts component to display the first N posts separately.
20
+ */
21
+ skipFirst?: number;
17
22
  /**
18
23
  * Language to use for post translations (default: 'en')
19
24
  */
@@ -23,9 +28,9 @@ export interface PostsListProps {
23
28
  */
24
29
  className?: string;
25
30
  /**
26
- * Custom className for post cards
31
+ * Custom className for post items
27
32
  */
28
- cardClassName?: string;
33
+ itemClassName?: string;
29
34
  /**
30
35
  * Show post date (default: true)
31
36
  */
@@ -47,5 +52,5 @@ export interface PostsListProps {
47
52
  */
48
53
  renderExcerpt?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
49
54
  }
50
- export declare function PostsList({ posts, baseUrl, platform, language, className, cardClassName, showDate, showExcerpt, showImages, renderTitle, renderExcerpt, }: PostsListProps): import("react/jsx-runtime").JSX.Element;
55
+ export declare function PostsList({ posts, baseUrl, platform, skipFirst, language, className, itemClassName, showDate, showExcerpt, showImages, renderTitle, renderExcerpt, }: PostsListProps): import("react/jsx-runtime").JSX.Element;
51
56
  //# sourceMappingURL=PostsList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostsList.d.ts","sourceRoot":"","sources":["../../src/components/PostsList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IACzF;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5F;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,OAAkB,EAClB,QAAiB,EACjB,QAAe,EACf,SAAc,EACd,aAAkB,EAClB,QAAe,EACf,WAAkB,EAClB,UAAiB,EACjB,WAAW,EACX,aAAa,GACd,EAAE,cAAc,2CAmFhB"}
1
+ {"version":3,"file":"PostsList.d.ts","sourceRoot":"","sources":["../../src/components/PostsList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IACzF;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5F;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,OAAkB,EAClB,QAAiB,EACjB,SAAa,EACb,QAAe,EACf,SAAc,EACd,aAAkB,EAClB,QAAe,EACf,WAAkB,EAClB,UAAiB,EACjB,WAAW,EACX,aAAa,GACd,EAAE,cAAc,2CAgFhB"}
@@ -1,26 +1,28 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- export function PostsList({ posts, baseUrl = '/posts', platform = 'blog', language = 'en', className = '', cardClassName = '', showDate = true, showExcerpt = true, showImages = true, renderTitle, renderExcerpt, }) {
3
+ export function PostsList({ posts, baseUrl = '/posts', platform = 'blog', skipFirst = 0, language = 'en', className = '', itemClassName = '', showDate = true, showExcerpt = true, showImages = true, renderTitle, renderExcerpt, }) {
4
4
  // Filter posts by platform if specified
5
5
  const filteredPosts = platform
6
6
  ? posts.filter(post => post.platform.toLowerCase() === platform.toLowerCase())
7
7
  : posts;
8
- if (filteredPosts.length === 0) {
8
+ // Skip first N posts
9
+ const displayPosts = skipFirst > 0 ? filteredPosts.slice(skipFirst) : filteredPosts;
10
+ if (displayPosts.length === 0) {
9
11
  return (_jsx("div", { className: `content-streamline-empty ${className}`, children: _jsx("p", { children: "No posts found." }) }));
10
12
  }
11
- return (_jsx("div", { className: `content-streamline-posts-list ${className}`, children: filteredPosts.map((post) => {
13
+ return (_jsx("div", { className: `content-streamline-list ${className}`, children: displayPosts.map((post) => {
12
14
  const translation = post.post_translations.find((t) => t.language === language) ||
13
15
  post.post_translations[0];
14
16
  if (!translation || !translation.slug)
15
17
  return null;
16
18
  const featuredImage = post.images.find((img) => img.ordering === 0) || post.images[0];
17
19
  const date = new Date(post.created_at);
18
- return (_jsxs("article", { className: `content-streamline-post-card ${cardClassName}`, children: [showImages && featuredImage && (_jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-post-image-link", children: _jsx("div", { className: "content-streamline-post-image", children: _jsx("img", { src: featuredImage.url, alt: featuredImage.alt || translation.title, loading: "lazy" }) }) })), _jsxs("div", { className: "content-streamline-post-content", children: [showDate && (_jsx("time", { className: "content-streamline-post-date", dateTime: post.created_at, children: date.toLocaleDateString('en-US', {
20
+ return (_jsxs("article", { className: `content-streamline-list-item ${itemClassName}`, children: [showImages && featuredImage && (_jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-list-image-link", children: _jsx("div", { className: "content-streamline-list-image", children: _jsx("img", { src: featuredImage.url, alt: featuredImage.alt || translation.title, loading: "lazy" }) }) })), _jsxs("div", { className: "content-streamline-list-content", children: [showDate && (_jsx("time", { className: "content-streamline-list-date", dateTime: post.created_at, children: date.toLocaleDateString('en-US', {
19
21
  year: 'numeric',
20
- month: 'long',
22
+ month: 'short',
21
23
  day: 'numeric',
22
- }) })), _jsx("h2", { className: "content-streamline-post-title", children: _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, children: renderTitle ? renderTitle(post, translation) : translation.title }) }), showExcerpt && translation.meta_description && (_jsx("p", { className: "content-streamline-post-excerpt", children: renderExcerpt
24
+ }) })), _jsx("h3", { className: "content-streamline-list-title", children: _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, children: renderTitle ? renderTitle(post, translation) : translation.title }) }), showExcerpt && translation.meta_description && (_jsx("p", { className: "content-streamline-list-excerpt", children: renderExcerpt
23
25
  ? renderExcerpt(post, translation)
24
- : translation.meta_description })), _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-post-link", children: "Read more" })] })] }, post.id));
26
+ : translation.meta_description }))] })] }, post.id));
25
27
  }) }));
26
28
  }
@@ -3,6 +3,8 @@
3
3
  */
4
4
  export { PostsList } from './PostsList.js';
5
5
  export { PostDetail } from './PostDetail.js';
6
+ export { LatestPosts } from './LatestPosts.js';
6
7
  export type { PostsListProps } from './PostsList.js';
7
8
  export type { PostDetailProps } from './PostDetail.js';
9
+ export type { LatestPostsProps } from './LatestPosts.js';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -3,5 +3,6 @@
3
3
  */
4
4
  export { PostsList } from './PostsList.js';
5
5
  export { PostDetail } from './PostDetail.js';
6
+ export { LatestPosts } from './LatestPosts.js';
6
7
  // Styles are available at '@content-streamline/nextjs/components/styles.css'
7
8
  // Users should import them in their app: import '@content-streamline/nextjs/components/styles.css'
@@ -3,107 +3,266 @@
3
3
  * You can override these with your own CSS
4
4
  */
5
5
 
6
- /* Posts List */
7
- .content-streamline-posts-list {
6
+ /* ==========================================================================
7
+ Latest Posts (Featured/Hero Section)
8
+ ========================================================================== */
9
+
10
+ .content-streamline-latest-posts {
8
11
  display: grid;
9
- grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
10
- gap: 2rem;
12
+ grid-template-columns: repeat(3, 1fr);
13
+ gap: 1.5rem;
11
14
  margin: 2rem 0;
12
15
  }
13
16
 
14
- .content-streamline-post-card {
17
+ /* First card spans full width on larger screens */
18
+ .content-streamline-latest-posts:has(.content-streamline-latest-card--featured) {
19
+ grid-template-columns: 1fr;
20
+ }
21
+
22
+ .content-streamline-latest-posts:has(.content-streamline-latest-card--featured) .content-streamline-latest-card--featured {
23
+ display: grid;
24
+ grid-template-columns: 1.2fr 1fr;
25
+ gap: 2rem;
26
+ }
27
+
28
+ .content-streamline-latest-posts:has(.content-streamline-latest-card--featured) .content-streamline-latest-card:not(.content-streamline-latest-card--featured) {
29
+ display: none;
30
+ }
31
+
32
+ /* When there are multiple cards, show grid */
33
+ .content-streamline-latest-posts:has(.content-streamline-latest-card:nth-child(2)) {
34
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
35
+ }
36
+
37
+ .content-streamline-latest-posts:has(.content-streamline-latest-card:nth-child(2)) .content-streamline-latest-card--featured {
38
+ grid-column: 1 / -1;
39
+ display: grid;
40
+ grid-template-columns: 1.5fr 1fr;
41
+ gap: 2.5rem;
42
+ }
43
+
44
+ .content-streamline-latest-posts:has(.content-streamline-latest-card:nth-child(2)) .content-streamline-latest-card:not(.content-streamline-latest-card--featured) {
45
+ display: flex;
46
+ flex-direction: column;
47
+ }
48
+
49
+ .content-streamline-latest-card {
15
50
  background: #ffffff;
16
- border-radius: 12px;
51
+ border-radius: 16px;
17
52
  overflow: hidden;
18
53
  border: 1px solid #e5e7eb;
19
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
20
- transition: transform 0.2s, box-shadow 0.2s;
21
- display: flex;
22
- flex-direction: column;
54
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
55
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
23
56
  }
24
57
 
25
- .content-streamline-post-card:hover {
58
+ .content-streamline-latest-card:hover {
26
59
  transform: translateY(-4px);
27
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
60
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
28
61
  }
29
62
 
30
- .content-streamline-post-image-link {
63
+ .content-streamline-latest-image-link {
31
64
  display: block;
32
65
  text-decoration: none;
33
- width: 100%;
66
+ overflow: hidden;
34
67
  }
35
68
 
36
- .content-streamline-post-image {
69
+ .content-streamline-latest-card--featured .content-streamline-latest-image-link {
70
+ border-radius: 16px 0 0 16px;
71
+ }
72
+
73
+ .content-streamline-latest-image {
37
74
  width: 100%;
38
75
  aspect-ratio: 16 / 10;
39
76
  overflow: hidden;
40
- background: #f3f4f6;
77
+ background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
78
+ }
79
+
80
+ .content-streamline-latest-card--featured .content-streamline-latest-image {
81
+ aspect-ratio: 16 / 11;
82
+ height: 100%;
41
83
  }
42
84
 
43
- .content-streamline-post-image img {
85
+ .content-streamline-latest-image img {
44
86
  width: 100%;
45
87
  height: 100%;
46
88
  object-fit: cover;
47
- transition: transform 0.3s;
89
+ transition: transform 0.4s ease;
48
90
  }
49
91
 
50
- .content-streamline-post-card:hover .content-streamline-post-image img {
92
+ .content-streamline-latest-card:hover .content-streamline-latest-image img {
51
93
  transform: scale(1.05);
52
94
  }
53
95
 
54
- .content-streamline-post-content {
96
+ .content-streamline-latest-content {
55
97
  padding: 1.5rem;
56
- flex: 1;
57
98
  display: flex;
58
99
  flex-direction: column;
100
+ justify-content: center;
59
101
  }
60
102
 
61
- .content-streamline-post-date {
62
- font-size: 0.875rem;
103
+ .content-streamline-latest-card--featured .content-streamline-latest-content {
104
+ padding: 2rem 2rem 2rem 0;
105
+ }
106
+
107
+ .content-streamline-latest-date {
108
+ font-size: 0.8125rem;
63
109
  color: #6b7280;
64
110
  margin-bottom: 0.75rem;
65
111
  text-transform: uppercase;
66
- letter-spacing: 0.05em;
112
+ letter-spacing: 0.08em;
113
+ font-weight: 500;
67
114
  }
68
115
 
69
- .content-streamline-post-title {
70
- font-size: 1.5rem;
71
- font-weight: 600;
72
- margin: 0 0 1rem 0;
73
- line-height: 1.3;
116
+ .content-streamline-latest-title {
117
+ font-size: 1.25rem;
118
+ font-weight: 700;
119
+ margin: 0 0 0.75rem 0;
120
+ line-height: 1.35;
74
121
  }
75
122
 
76
- .content-streamline-post-title a {
123
+ .content-streamline-latest-card--featured .content-streamline-latest-title {
124
+ font-size: 1.75rem;
125
+ margin-bottom: 1rem;
126
+ }
127
+
128
+ .content-streamline-latest-title a {
77
129
  color: #111827;
78
130
  text-decoration: none;
79
- transition: color 0.2s;
131
+ transition: color 0.2s ease;
80
132
  }
81
133
 
82
- .content-streamline-post-title a:hover {
134
+ .content-streamline-latest-title a:hover {
83
135
  color: #3b82f6;
84
136
  }
85
137
 
86
- .content-streamline-post-excerpt {
138
+ .content-streamline-latest-excerpt {
87
139
  color: #6b7280;
88
140
  line-height: 1.6;
89
141
  margin: 0 0 1rem 0;
90
- flex: 1;
142
+ font-size: 0.9375rem;
143
+ display: -webkit-box;
144
+ -webkit-line-clamp: 3;
145
+ -webkit-box-orient: vertical;
146
+ overflow: hidden;
147
+ }
148
+
149
+ .content-streamline-latest-card--featured .content-streamline-latest-excerpt {
150
+ font-size: 1rem;
151
+ -webkit-line-clamp: 4;
91
152
  }
92
153
 
93
- .content-streamline-post-link {
154
+ .content-streamline-latest-link {
94
155
  color: #3b82f6;
95
156
  text-decoration: none;
96
- font-weight: 500;
157
+ font-weight: 600;
158
+ font-size: 0.9375rem;
159
+ transition: color 0.2s ease;
97
160
  margin-top: auto;
98
- transition: color 0.2s;
99
161
  }
100
162
 
101
- .content-streamline-post-link:hover {
163
+ .content-streamline-latest-link:hover {
102
164
  color: #2563eb;
103
- text-decoration: underline;
104
165
  }
105
166
 
106
- /* Post Detail */
167
+ /* ==========================================================================
168
+ Posts List (Vertical List with Image on Left)
169
+ ========================================================================== */
170
+
171
+ .content-streamline-list {
172
+ display: flex;
173
+ flex-direction: column;
174
+ gap: 1.25rem;
175
+ margin: 2rem 0;
176
+ }
177
+
178
+ .content-streamline-list-item {
179
+ display: grid;
180
+ grid-template-columns: 180px 1fr;
181
+ gap: 1.25rem;
182
+ padding: 1rem;
183
+ background: #ffffff;
184
+ border-radius: 12px;
185
+ border: 1px solid #e5e7eb;
186
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
187
+ }
188
+
189
+ .content-streamline-list-item:hover {
190
+ border-color: #d1d5db;
191
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
192
+ }
193
+
194
+ .content-streamline-list-image-link {
195
+ display: block;
196
+ text-decoration: none;
197
+ border-radius: 8px;
198
+ overflow: hidden;
199
+ }
200
+
201
+ .content-streamline-list-image {
202
+ width: 100%;
203
+ aspect-ratio: 16 / 11;
204
+ overflow: hidden;
205
+ background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
206
+ border-radius: 8px;
207
+ }
208
+
209
+ .content-streamline-list-image img {
210
+ width: 100%;
211
+ height: 100%;
212
+ object-fit: cover;
213
+ transition: transform 0.3s ease;
214
+ }
215
+
216
+ .content-streamline-list-item:hover .content-streamline-list-image img {
217
+ transform: scale(1.05);
218
+ }
219
+
220
+ .content-streamline-list-content {
221
+ display: flex;
222
+ flex-direction: column;
223
+ justify-content: center;
224
+ padding: 0.25rem 0;
225
+ }
226
+
227
+ .content-streamline-list-date {
228
+ font-size: 0.75rem;
229
+ color: #9ca3af;
230
+ margin-bottom: 0.375rem;
231
+ font-weight: 500;
232
+ }
233
+
234
+ .content-streamline-list-title {
235
+ font-size: 1.0625rem;
236
+ font-weight: 600;
237
+ margin: 0 0 0.5rem 0;
238
+ line-height: 1.4;
239
+ }
240
+
241
+ .content-streamline-list-title a {
242
+ color: #111827;
243
+ text-decoration: none;
244
+ transition: color 0.2s ease;
245
+ }
246
+
247
+ .content-streamline-list-title a:hover {
248
+ color: #3b82f6;
249
+ }
250
+
251
+ .content-streamline-list-excerpt {
252
+ color: #6b7280;
253
+ line-height: 1.5;
254
+ margin: 0;
255
+ font-size: 0.875rem;
256
+ display: -webkit-box;
257
+ -webkit-line-clamp: 2;
258
+ -webkit-box-orient: vertical;
259
+ overflow: hidden;
260
+ }
261
+
262
+ /* ==========================================================================
263
+ Post Detail
264
+ ========================================================================== */
265
+
107
266
  .content-streamline-post-detail {
108
267
  max-width: 800px;
109
268
  margin: 0 auto;
@@ -298,6 +457,10 @@
298
457
  text-align: center;
299
458
  }
300
459
 
460
+ /* ==========================================================================
461
+ Empty States
462
+ ========================================================================== */
463
+
301
464
  .content-streamline-empty,
302
465
  .content-streamline-post-not-found {
303
466
  text-align: center;
@@ -305,13 +468,59 @@
305
468
  color: #6b7280;
306
469
  }
307
470
 
308
- /* Responsive */
471
+ /* ==========================================================================
472
+ Responsive
473
+ ========================================================================== */
474
+
475
+ @media (max-width: 1024px) {
476
+ .content-streamline-latest-posts:has(.content-streamline-latest-card:nth-child(2)) .content-streamline-latest-card--featured {
477
+ grid-template-columns: 1fr 1fr;
478
+ gap: 1.5rem;
479
+ }
480
+ }
481
+
309
482
  @media (max-width: 768px) {
310
- .content-streamline-posts-list {
483
+ /* Latest Posts */
484
+ .content-streamline-latest-posts {
311
485
  grid-template-columns: 1fr;
312
- gap: 1.5rem;
486
+ gap: 1.25rem;
487
+ }
488
+
489
+ .content-streamline-latest-posts:has(.content-streamline-latest-card--featured) .content-streamline-latest-card--featured,
490
+ .content-streamline-latest-posts:has(.content-streamline-latest-card:nth-child(2)) .content-streamline-latest-card--featured {
491
+ grid-template-columns: 1fr;
492
+ gap: 0;
313
493
  }
314
494
 
495
+ .content-streamline-latest-card--featured .content-streamline-latest-image-link {
496
+ border-radius: 16px 16px 0 0;
497
+ }
498
+
499
+ .content-streamline-latest-card--featured .content-streamline-latest-content {
500
+ padding: 1.5rem;
501
+ }
502
+
503
+ .content-streamline-latest-card--featured .content-streamline-latest-title {
504
+ font-size: 1.375rem;
505
+ }
506
+
507
+ /* Posts List */
508
+ .content-streamline-list-item {
509
+ grid-template-columns: 120px 1fr;
510
+ gap: 1rem;
511
+ padding: 0.75rem;
512
+ }
513
+
514
+ .content-streamline-list-title {
515
+ font-size: 0.9375rem;
516
+ }
517
+
518
+ .content-streamline-list-excerpt {
519
+ font-size: 0.8125rem;
520
+ -webkit-line-clamp: 2;
521
+ }
522
+
523
+ /* Post Detail */
315
524
  .content-streamline-post-detail {
316
525
  padding: 1.5rem 1rem;
317
526
  }
@@ -329,3 +538,14 @@
329
538
  }
330
539
  }
331
540
 
541
+ @media (max-width: 480px) {
542
+ /* Posts List - stack vertically on very small screens */
543
+ .content-streamline-list-item {
544
+ grid-template-columns: 1fr;
545
+ gap: 0.75rem;
546
+ }
547
+
548
+ .content-streamline-list-image {
549
+ aspect-ratio: 16 / 9;
550
+ }
551
+ }
package/dist/index.d.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  * Content Streamline Next.js Library
3
3
  * Main entry point
4
4
  */
5
- export type { Post, PostDetailResponse, PostTranslation, PostImage, PostsResponse, Pagination, } from './types/index.js';
5
+ export type { Post, PostDetailResponse, PostTranslation, PostImage, PostsResponse, Pagination, CacheMetadata, } from './types/index.js';
6
6
  export { getAllPosts, getPost, getPostBySlug, } from './server/index.js';
7
- export { PostsList, PostDetail, } from './components/index.js';
8
- export type { PostsListProps, PostDetailProps, } from './components/index.js';
7
+ export { clearCache, isAPIConfigured, } from './cache/file-cache.js';
8
+ export { PostsList, PostDetail, LatestPosts, } from './components/index.js';
9
+ export type { PostsListProps, PostDetailProps, LatestPostsProps, } from './components/index.js';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,YAAY,EACV,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,OAAO,EACP,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,SAAS,EACT,UAAU,GACX,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,cAAc,EACd,eAAe,GAChB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,YAAY,EACV,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,UAAU,EACV,aAAa,GACd,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,OAAO,EACP,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,UAAU,EACV,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,GACjB,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -4,5 +4,7 @@
4
4
  */
5
5
  // Export server functions
6
6
  export { getAllPosts, getPost, getPostBySlug, } from './server/index.js';
7
+ // Export cache utilities
8
+ export { clearCache, isAPIConfigured, } from './cache/file-cache.js';
7
9
  // Export components
8
- export { PostsList, PostDetail, } from './components/index.js';
10
+ export { PostsList, PostDetail, LatestPosts, } from './components/index.js';
@@ -6,6 +6,10 @@ import type { Post, PostDetailResponse } from '../types/index.js';
6
6
  /**
7
7
  * Get all posts (with automatic cache refresh)
8
8
  * Use this in Server Components or API Routes
9
+ *
10
+ * Note: Only successful API responses are cached. Failed API calls
11
+ * (network errors, auth failures, etc.) will NOT be cached, allowing
12
+ * subsequent calls to retry the API.
9
13
  */
10
14
  export declare function getAllPosts(options?: {
11
15
  maxCacheAge?: number;
@@ -15,6 +19,9 @@ export declare function getAllPosts(options?: {
15
19
  /**
16
20
  * Get a single post by ID
17
21
  * Use this in Server Components or API Routes
22
+ *
23
+ * Note: Only successful API responses are cached. Failed API calls
24
+ * will NOT be cached, allowing subsequent calls to retry the API.
18
25
  */
19
26
  export declare function getPost(id: number, options?: {
20
27
  language?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAkBlE;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CA2ClB;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAiCpC;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAGD,YAAY,EACV,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,OAAO,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AA+BlE;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CA2ElB;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAoDpC;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAGD,YAAY,EACV,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,mBAAmB,CAAC"}