@windstream/react-shared-components 0.2.31 → 0.2.33

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windstream/react-shared-components",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "type": "module",
5
5
  "description": "Shared React components for Kinetic applications",
6
6
  "main": "dist/index.js",
@@ -7,6 +7,7 @@ import { BlogCard } from "@shared/contentful/blocks/cards/blog-card";
7
7
 
8
8
  export function BlogGridBase({
9
9
  title = "recent articles",
10
+ description,
10
11
  paginatedArticles,
11
12
  totalArticles,
12
13
  currentPage,
@@ -16,6 +17,7 @@ export function BlogGridBase({
16
17
  onCategoryChange,
17
18
  onPageChange,
18
19
  getPageHref,
20
+ asList = false,
19
21
  imageComponent,
20
22
  }: BlogGridBaseProps) {
21
23
  function handleCategoryChange(value: unknown) {
@@ -36,10 +38,14 @@ export function BlogGridBase({
36
38
  >
37
39
  {/* Controls bar */}
38
40
  <div className="mx-auto flex max-w-[1200px] flex-wrap items-center justify-between gap-3 px-6 pb-6">
41
+ {description && (
42
+ <Text as="p" className="body1 mb-4 text-center md:mb-6">
43
+ {description}
44
+ </Text>
45
+ )}
39
46
  <Text className="w-full text-center text-heading5 font-black lowercase text-text-secondary md:w-auto md:text-left">
40
47
  {title}
41
48
  </Text>
42
-
43
49
  {categoryOptions.length > 0 && (
44
50
  <div className="flex w-full flex-col-reverse gap-3 md:w-auto md:flex-row md:items-center md:gap-6">
45
51
  {/* Showing count */}
@@ -66,9 +72,15 @@ export function BlogGridBase({
66
72
  )}
67
73
  </div>
68
74
 
69
- {/* Articles grid */}
75
+ {/* Articles grid / list */}
70
76
  {paginatedArticles.length > 0 ? (
71
- <div className="mx-auto grid max-w-[1200px] grid-cols-1 gap-6 px-5 pb-16 sm:grid-cols-2 lg:grid-cols-3">
77
+ <div
78
+ className={
79
+ asList
80
+ ? "mx-auto flex max-w-[1200px] flex-col gap-1 px-5 pb-16"
81
+ : "mx-auto grid max-w-[1200px] grid-cols-1 gap-6 px-5 pb-16 sm:grid-cols-2 lg:grid-cols-3"
82
+ }
83
+ >
72
84
  {paginatedArticles.map((article, index) => {
73
85
  const href = article.slug.startsWith("/")
74
86
  ? article.slug
@@ -95,6 +107,7 @@ export function BlogGridBase({
95
107
  category={article.category}
96
108
  image={coverImage}
97
109
  imageComponent={imageComponent}
110
+ asGrid={!asList}
98
111
  index={index}
99
112
  />
100
113
  );
@@ -18,6 +18,7 @@ export interface BlogCardProps {
18
18
 
19
19
  export interface BlogGridBaseProps {
20
20
  title?: string;
21
+ description?: string;
21
22
  paginatedArticles: BlogCardProps[];
22
23
  totalArticles: number;
23
24
  currentPage: number;
@@ -27,6 +28,8 @@ export interface BlogGridBaseProps {
27
28
  onCategoryChange?: (category: BlogCategoryOption) => void;
28
29
  onPageChange?: (page: number) => void;
29
30
  getPageHref?: (page: number) => string;
31
+ /** When true, render cards in a single-column list instead of a grid. */
32
+ asList?: boolean;
30
33
  /** Pass next/image's Image component here to enable image optimization */
31
34
  imageComponent?: React.ComponentType<BlogCardImageProps>;
32
35
  }
@@ -27,57 +27,66 @@ export const BlogCard: React.FC<BlogCardProps> = ({
27
27
  }: BlogCardProps) => {
28
28
  const parentClassName = asGrid
29
29
  ? "flex h-full flex-col overflow-hidden rounded-card-lg bg-white shadow-drop transition-all duration-200 hover:-translate-y-0.5 hover:shadow-cardDrop"
30
- : `callout-card w-full flex h-full flex-col overflow-hidden rounded-card-lg bg-white shadow-drop transition-all duration-200 hover:-translate-y-0.5 hover:shadow-cardDrop`;
30
+ : `callout-card w-full flex h-full flex-col overflow-hidden`;
31
31
 
32
32
  return (
33
33
  <article
34
34
  className={parentClassName}
35
35
  data-section-type={"blog-card"}
36
36
  data-section-index={index}
37
+ data-view-mode={asGrid ? "grid" : "list"}
37
38
  >
38
- {/* Image */}
39
- <Link href={href} tabIndex={-1} aria-hidden="true" className="block">
40
- <div className="h-[232px] w-full flex-shrink-0 overflow-hidden bg-gray-100">
41
- {image ? (
42
- ImageComponent ? (
43
- <ImageComponent
44
- src={image.src}
45
- alt={image.alt}
46
- width={image.width}
47
- height={image.height}
48
- className="h-full w-full object-cover transition-transform duration-300 hover:scale-[1.03]"
49
- sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
50
- />
39
+ {/* Image (hidden in list view) */}
40
+ {asGrid && (
41
+ <Link href={href} tabIndex={-1} aria-hidden="true" className="block">
42
+ <div className="h-[232px] w-full flex-shrink-0 overflow-hidden bg-gray-100">
43
+ {image ? (
44
+ ImageComponent ? (
45
+ <ImageComponent
46
+ src={image.src}
47
+ alt={image.alt}
48
+ width={image.width}
49
+ height={image.height}
50
+ className="h-full w-full object-cover transition-transform duration-300 hover:scale-[1.03]"
51
+ sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
52
+ />
53
+ ) : (
54
+ <img
55
+ src={image.src}
56
+ alt={image.alt}
57
+ width={image.width}
58
+ height={image.height}
59
+ loading="lazy"
60
+ decoding="async"
61
+ className="h-full w-full object-cover transition-transform duration-300 hover:scale-[1.03]"
62
+ />
63
+ )
51
64
  ) : (
52
- <img
53
- src={image.src}
54
- alt={image.alt}
55
- width={image.width}
56
- height={image.height}
57
- loading="lazy"
58
- decoding="async"
59
- className="h-full w-full object-cover transition-transform duration-300 hover:scale-[1.03]"
65
+ <div
66
+ className="h-full w-full bg-gradient-to-br from-gray-200 to-gray-100"
67
+ aria-hidden="true"
60
68
  />
61
- )
62
- ) : (
63
- <div
64
- className="h-full w-full bg-gradient-to-br from-gray-200 to-gray-100"
65
- aria-hidden="true"
66
- />
67
- )}
68
- </div>
69
- </Link>
69
+ )}
70
+ </div>
71
+ </Link>
72
+ )}
70
73
 
71
74
  {/* Body */}
72
- <div className="flex flex-1 flex-col gap-5 p-6 md:p-8">
75
+ <div
76
+ className={`flex flex-1 flex-col ${
77
+ asGrid ? "gap-5 p-6 md:p-8" : "gap-1 border-b p-3 md:p-4"
78
+ }`}
79
+ >
73
80
  {/* Meta: category + date */}
74
81
  <div className="flex items-center gap-2 text-[13px]">
75
- <span className="body2 text-text-brand">{category}</span>
82
+ {asGrid && <span className="body2 text-text-brand">{category}</span>}
76
83
  {date && (
77
84
  <>
78
- <span className="footnote text-text" aria-hidden="true">
79
-
80
- </span>
85
+ {asGrid && (
86
+ <span className="footnote text-text" aria-hidden="true">
87
+
88
+ </span>
89
+ )}
81
90
  <time className="body2 text-text">{date}</time>
82
91
  </>
83
92
  )}
@@ -100,25 +109,27 @@ export const BlogCard: React.FC<BlogCardProps> = ({
100
109
  </Text>
101
110
  )}
102
111
 
103
- {/* Read more */}
104
- <Link
105
- href={href}
106
- className="group mt-auto inline-flex items-center justify-start gap-2 pt-3 text-sm text-text-brand no-underline"
107
- aria-label={`${readMoreText} about ${title || "this article"}`}
108
- >
109
- <Text
112
+ {/* Read more (hidden in list view) */}
113
+ {asGrid && (
114
+ <Link
115
+ href={href}
116
+ className="group mt-auto inline-flex items-center justify-start gap-2 pt-3 text-sm text-text-brand no-underline"
110
117
  aria-label={`${readMoreText} about ${title || "this article"}`}
111
- className="label1 text-nowrap"
112
118
  >
113
- {readMoreText}
114
- </Text>
115
- <MaterialIcon
116
- name="expand_circle_right"
117
- fill={1}
118
- size={24}
119
- weight="200"
120
- />
121
- </Link>
119
+ <Text
120
+ aria-label={`${readMoreText} about ${title || "this article"}`}
121
+ className="label1 text-nowrap"
122
+ >
123
+ {readMoreText}
124
+ </Text>
125
+ <MaterialIcon
126
+ name="expand_circle_right"
127
+ fill={1}
128
+ size={24}
129
+ weight="200"
130
+ />
131
+ </Link>
132
+ )}
122
133
  </div>
123
134
  </article>
124
135
  );
@@ -33,7 +33,9 @@ const defaultOptions: Options = {
33
33
  },
34
34
  renderNode: {
35
35
  [BLOCKS.PARAGRAPH]: (node, children) => (
36
- <div className="body3 mb-4">{isEmptyParagraph(node) ? "\n" : children}</div>
36
+ <div className="body3 mb-4 whitespace-pre-line">
37
+ {isEmptyParagraph(node) ? "\n" : children}
38
+ </div>
37
39
  ),
38
40
  [BLOCKS.HEADING_1]: (node, children) => {
39
41
  return (
@@ -165,7 +167,9 @@ export function renderContentfulRichText(
165
167
  ...options?.renderNode,
166
168
  // Logic for links based on the isTargetBlank flag
167
169
  [BLOCKS.PARAGRAPH]: (node, children) => (
168
- <div className={className}>{isEmptyParagraph(node) ? "\n" : children}</div>
170
+ <div className={[className, "whitespace-pre-line"].filter(Boolean).join(" ")}>
171
+ {isEmptyParagraph(node) ? "\n" : children}
172
+ </div>
169
173
  ),
170
174
  [INLINES.HYPERLINK]: (node, children) => {
171
175
  const url = (node as any)?.data?.uri as string;