@windstream/react-shared-components 0.2.2 → 0.2.3

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.
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useEffect, useRef } from "react";
3
4
  import { BlogCategoryOption, BlogGridProps } from "./types";
4
5
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
5
6
 
@@ -19,6 +20,15 @@ export function BlogGrid({
19
20
  const router = useRouter();
20
21
  const pathname = usePathname();
21
22
  const searchParams = useSearchParams();
23
+ const isInitialMount = useRef(true);
24
+
25
+ useEffect(() => {
26
+ if (isInitialMount.current) {
27
+ isInitialMount.current = false;
28
+ return;
29
+ }
30
+ window.scrollTo({ top: 0, behavior: "smooth" });
31
+ }, [currentPage]);
22
32
 
23
33
  function updateQueryParams(key: string, value: string) {
24
34
  const params = new URLSearchParams(searchParams.toString());
@@ -40,9 +50,19 @@ export function BlogGrid({
40
50
  updateQueryParams("category", cat.value === "all" ? "" : cat.value);
41
51
  }
42
52
 
43
- function handlePageChange(page: number) {
44
- updateQueryParams("page", page === 1 ? "" : page.toString());
45
- window.scrollTo({ top: 0, behavior: "smooth" });
53
+ function handlePageChange(_page: number) {
54
+ // Scroll is handled by the useEffect on currentPage change
55
+ }
56
+
57
+ function getPageHref(page: number): string {
58
+ const params = new URLSearchParams(searchParams.toString());
59
+ if (page === 1) {
60
+ params.delete("page");
61
+ } else {
62
+ params.set("page", page.toString());
63
+ }
64
+ const qs = params.toString();
65
+ return qs ? `${pathname}?${qs}` : pathname;
46
66
  }
47
67
 
48
68
  return (
@@ -125,6 +145,7 @@ export function BlogGrid({
125
145
  <Pagination
126
146
  currentPage={currentPage}
127
147
  totalPages={totalPages}
148
+ getPageHref={getPageHref}
128
149
  onPageChange={handlePageChange}
129
150
  ariaLabel="Blog article pagination"
130
151
  />
@@ -15,6 +15,7 @@ export function BlogGridBase({
15
15
  categoryOptions,
16
16
  onCategoryChange,
17
17
  onPageChange,
18
+ getPageHref,
18
19
  imageComponent,
19
20
  }: BlogGridBaseProps) {
20
21
  function handleCategoryChange(value: unknown) {
@@ -110,6 +111,7 @@ export function BlogGridBase({
110
111
  <Pagination
111
112
  currentPage={currentPage}
112
113
  totalPages={totalPages}
114
+ getPageHref={getPageHref}
113
115
  onPageChange={handlePageChange}
114
116
  ariaLabel="Blog article pagination"
115
117
  />
@@ -26,6 +26,7 @@ export interface BlogGridBaseProps {
26
26
  categoryOptions: BlogCategoryOption[];
27
27
  onCategoryChange?: (category: BlogCategoryOption) => void;
28
28
  onPageChange?: (page: number) => void;
29
+ getPageHref: (page: number) => string;
29
30
  /** Pass next/image's Image component here to enable image optimization */
30
31
  imageComponent?: React.ComponentType<BlogCardImageProps>;
31
32
  }
@@ -1,95 +1,95 @@
1
- import React from "react";
2
- import { BreadcrumbNavigationProps } from "./types";
3
-
4
- import { MaterialIcon } from "@shared/components/material-icon";
5
- import { Text } from "@shared/components/text";
6
-
7
- export const BreadcrumbNavigation: React.FC<
8
- BreadcrumbNavigationProps
9
- > = props => {
10
- const {
11
- links = [],
12
- textColor = "dark",
13
- mobileTextColor,
14
- desktopTextColor,
15
- maxWidth = true,
16
- float = "desktop",
17
- } = props;
18
- const resolvedMobile =
19
- mobileTextColor ?? (float === "desktop" ? "dark" : textColor);
20
- const resolvedDesktop =
21
- desktopTextColor ?? (float === "mobile" ? "dark" : textColor);
22
- const mobileColor = resolvedMobile === "dark" ? "text-text" : "text-white";
23
- const desktopColor =
24
- resolvedDesktop === "dark" ? "xl:text-text" : "xl:text-white";
25
- const color = `${mobileColor} ${desktopColor}`;
26
-
27
- const olPosition =
28
- float === "always"
29
- ? "absolute"
30
- : float === "mobile"
31
- ? "absolute xl:relative"
32
- : float === "desktop"
33
- ? "relative xl:absolute"
34
- : "relative";
35
-
36
- if (!links.length) return null;
37
- return (
38
- <nav
39
- aria-label="Breadcrumb"
40
- className={`${maxWidth ? `${float === "none" && "mx-5"} max-w-120 xl:mx-auto` : "mx-auto"} relative`}
41
- >
42
- <ol
43
- className={`${float === "always" && "px-7 md:px-14"} right-0 z-10 mx-0 flex w-full flex-nowrap items-center gap-2 pb-0 pt-8 ${olPosition} xl:mx-auto xl:flex-wrap xl:px-3`}
44
- >
45
- {links.map((link, index) => {
46
- const { image, ...linkProps } = link;
47
- const imageSrc = image?.url
48
- ? image.url.startsWith("//")
49
- ? `https:${image.url}`
50
- : image.url
51
- : "";
52
- const isLast = index === links.length - 1;
53
- return !isLast ? (
54
- <li key={index} className="flex flex-none items-center">
55
- {imageSrc && (
56
- <img
57
- src={imageSrc}
58
- alt={image?.alt || ""}
59
- width={20}
60
- height={20}
61
- className="mr-2 h-10 w-10"
62
- />
63
- )}
64
- <a
65
- href={linkProps.href}
66
- className={`label3 mr-2 whitespace-nowrap ${color} hover:underline`}
67
- >
68
- {linkProps.buttonLabel}
69
- </a>
70
-
71
- <MaterialIcon name="chevron_right" className={`${color} `} />
72
- </li>
73
- ) : (
74
- <li key={index} className="flex min-w-0 items-center">
75
- {imageSrc && (
76
- <img
77
- src={imageSrc}
78
- alt={image?.alt || ""}
79
- width={20}
80
- height={20}
81
- className="mr-2 h-10 w-10"
82
- />
83
- )}
84
- <Text as="span" className={`body3 mr-2 break-words ${color}`}>
85
- {link.buttonLabel}
86
- </Text>
87
- </li>
88
- );
89
- })}
90
- </ol>
91
- </nav>
92
- );
93
- };
94
-
95
- export default BreadcrumbNavigation;
1
+ import React from "react";
2
+ import { BreadcrumbNavigationProps } from "./types";
3
+
4
+ import { MaterialIcon } from "@shared/components/material-icon";
5
+ import { Text } from "@shared/components/text";
6
+
7
+ export const BreadcrumbNavigation: React.FC<
8
+ BreadcrumbNavigationProps
9
+ > = props => {
10
+ const {
11
+ links = [],
12
+ textColor = "dark",
13
+ mobileTextColor,
14
+ desktopTextColor,
15
+ maxWidth = true,
16
+ float = "desktop",
17
+ } = props;
18
+ const resolvedMobile =
19
+ mobileTextColor ?? (float === "desktop" ? "dark" : textColor);
20
+ const resolvedDesktop =
21
+ desktopTextColor ?? (float === "mobile" ? "dark" : textColor);
22
+ const mobileColor = resolvedMobile === "dark" ? "text-text" : "text-white";
23
+ const desktopColor =
24
+ resolvedDesktop === "dark" ? "xl:text-text" : "xl:text-white";
25
+ const color = `${mobileColor} ${desktopColor}`;
26
+
27
+ const olPosition =
28
+ float === "always"
29
+ ? "absolute"
30
+ : float === "mobile"
31
+ ? "absolute xl:relative"
32
+ : float === "desktop"
33
+ ? "relative xl:absolute"
34
+ : "relative";
35
+
36
+ if (!links.length) return null;
37
+ return (
38
+ <nav
39
+ aria-label="Breadcrumb"
40
+ className={`${maxWidth ? `${float === "none" && "mx-5"} max-w-120 xl:mx-auto` : "mx-auto"} relative`}
41
+ >
42
+ <ol
43
+ className={`${float === "always" && "px-7 md:px-14"} right-0 z-10 mx-0 flex w-full flex-nowrap items-center gap-2 pb-0 pt-8 ${olPosition} xl:mx-auto xl:flex-wrap xl:px-3`}
44
+ >
45
+ {links.map((link, index) => {
46
+ const { image, ...linkProps } = link;
47
+ const imageSrc = image?.url
48
+ ? image.url.startsWith("//")
49
+ ? `https:${image.url}`
50
+ : image.url
51
+ : "";
52
+ const isLast = index === links.length - 1;
53
+ return !isLast ? (
54
+ <li key={index} className="flex flex-none items-center">
55
+ {imageSrc && (
56
+ <img
57
+ src={imageSrc}
58
+ alt={image?.alt || ""}
59
+ width={20}
60
+ height={20}
61
+ className="mr-2 h-10 w-10"
62
+ />
63
+ )}
64
+ <a
65
+ href={linkProps.href}
66
+ className={`label3 mr-2 whitespace-nowrap ${color} hover:underline`}
67
+ >
68
+ {linkProps.buttonLabel}
69
+ </a>
70
+
71
+ <MaterialIcon name="chevron_right" className={`${color} `} />
72
+ </li>
73
+ ) : (
74
+ <li key={index} className="flex min-w-0 items-center">
75
+ {imageSrc && (
76
+ <img
77
+ src={imageSrc}
78
+ alt={image?.alt || ""}
79
+ width={20}
80
+ height={20}
81
+ className="mr-2 h-10 w-10"
82
+ />
83
+ )}
84
+ <Text as="span" className={`body3 mr-2 break-words ${color}`}>
85
+ {link.buttonLabel}
86
+ </Text>
87
+ </li>
88
+ );
89
+ })}
90
+ </ol>
91
+ </nav>
92
+ );
93
+ };
94
+
95
+ export default BreadcrumbNavigation;
@@ -95,7 +95,7 @@ export const Callout: React.FC<CalloutProps> = ({
95
95
  // flex-wrap layout that always renders multi-column on md+ and
96
96
  // full-width on mobile. This matches the pre-0.1.79 DOM and keeps
97
97
  // consumers like SMB-browse rendering correctly without changes.
98
- const isStackMode = cardsWidth === false;
98
+ const isStackMode = cardsWidth === false && cardType !== "blog";
99
99
  const gridClass = isStackMode
100
100
  ? cx(
101
101
  "flex flex-col items-stretch self-stretch",
@@ -149,18 +149,13 @@ describe("BlogCard", () => {
149
149
  expect(article?.className).toContain("callout-card");
150
150
  });
151
151
 
152
- it("applies lgWidth and mdWidth in non-grid mode", () => {
152
+ it("applies h-full in non-grid mode for equal height cards", () => {
153
153
  const { container } = render(
154
- <BlogCard
155
- {...defaultProps}
156
- asGrid={false}
157
- lgWidth="lg:w-1/3"
158
- mdWidth="md:w-1/2"
159
- />
154
+ <BlogCard {...defaultProps} asGrid={false} />
160
155
  );
161
156
  const article = container.querySelector("article");
162
- expect(article?.className).toContain("lg:w-1/3");
163
- expect(article?.className).toContain("md:w-1/2");
157
+ expect(article?.className).toContain("h-full");
158
+ expect(article?.className).toContain("flex-col");
164
159
  });
165
160
 
166
161
  it("applies data-section-type attribute", () => {
@@ -23,14 +23,11 @@ export const BlogCard: React.FC<BlogCardProps> = ({
23
23
  imageComponent: ImageComponent,
24
24
  readMoreText = "Read more",
25
25
  asGrid = true,
26
- lgWidth,
27
- mdWidth,
28
26
  index,
29
27
  }: BlogCardProps) => {
30
- const basehwClass = "lg:w-[calc(33.3333%-1rem)] md:w-[calc(50%-1rem)]";
31
28
  const parentClassName = asGrid
32
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"
33
- : `callout-card ${basehwClass} ${lgWidth} ${mdWidth} w-full 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`;
34
31
 
35
32
  return (
36
33
  <article
@@ -28,7 +28,5 @@ export interface BlogCardProps {
28
28
  imageComponent?: React.ComponentType<BlogCardImageProps>;
29
29
  readMoreText?: string;
30
30
  asGrid?: boolean;
31
- lgWidth?: string;
32
- mdWidth?: string;
33
31
  index?: number;
34
32
  }
@@ -79,10 +79,10 @@ export const FloatingImageCard: React.FC<FloatingImageCardProps> = ({
79
79
  src={image.href}
80
80
  alt={image.title ?? title ?? "card-image"}
81
81
  width={image.width || 400}
82
- height={image.height || 285}
82
+ height={image.height || 330}
83
83
  onClick={handleImageClick}
84
84
  className={cx(
85
- "h-full max-h-[285px] w-full rounded-[40px] object-cover",
85
+ "h-full max-h-[330px] w-full rounded-[40px] object-cover",
86
86
  imageLinkAvailableClass
87
87
  )}
88
88
  />
@@ -1,147 +1,147 @@
1
- import React from "react";
2
- import { FindKineticProps, ThemeKey } from "./types";
3
-
4
- import { Link } from "@shared/components/link";
5
- import { Text } from "@shared/components/text";
6
- import { NextImage } from "@shared/next";
7
-
8
- export const FindKinetic: React.FC<FindKineticProps> = ({
9
- background = "white",
10
- description,
11
- enableHeading,
12
- image,
13
- list = [],
14
- localPathPrefix = "/local",
15
- subTitle,
16
- title,
17
- color = "dark",
18
- maxWidth = true,
19
- }) => {
20
- const bgColorClasses: Record<ThemeKey, string> = {
21
- blue: "bg-[#07B2E2]",
22
- green: "bg-[#26B170]",
23
- yellow: "bg-[#F5FF1E]",
24
- purple: "bg-[#931D69]",
25
- white: "bg-white",
26
- navy: "bg-[#00002D]",
27
- };
28
- const normalizedLocalPathPrefix = localPathPrefix
29
- ? `/${localPathPrefix.replace(/^\/+|\/+$/g, "")}`
30
- : "";
31
-
32
- const getLocationHref = (code: string) =>
33
- `${normalizedLocalPathPrefix}/${code.toLowerCase()}`;
34
-
35
- return (
36
- <div
37
- className={`${bgColorClasses[background]} component-container overflow-hidden ${color == "dark" ? "text-text" : "text-white"}`}
38
- >
39
- <div
40
- className={`${maxWidth ? "max-w-120 xl:mx-auto" : ""} mx-5 mb-5 ${image ? "mt-12" : "mt-0 xl:mt-0"} xl:my-20`}
41
- >
42
- {title && (
43
- <Text
44
- as={enableHeading ? "h1" : "h2"}
45
- className="heading2 md:heading1 md:text-center"
46
- >
47
- {title}
48
- </Text>
49
- )}
50
- {subTitle && (
51
- <Text
52
- as={enableHeading ? "h2" : "h3"}
53
- className="subheading1 mt-4 md:text-center"
54
- >
55
- {subTitle}
56
- </Text>
57
- )}
58
-
59
- <div
60
- className={`mt-8 flex flex-col items-center xl:flex-row ${image ? "xl:mt-16" : "xl:mt-10"} gap-10`}
61
- >
62
- <div
63
- className={`flex flex-col ${image ? "xl:items-start" : "items-center"} gap-10 self-stretch xl:flex-[1_0_0]`}
64
- >
65
- {description && (
66
- <Text as="p" className="body1">
67
- {description}
68
- </Text>
69
- )}
70
- {list.length > 0 && (
71
- <>
72
- <ul className="grid w-full grid-cols-2 gap-x-6 gap-y-5 md:hidden">
73
- {(() => {
74
- const sortedList = [...list].sort((a, b) =>
75
- a.name.localeCompare(b.name)
76
- );
77
- const numColumns = 2;
78
- const numRows = Math.ceil(sortedList.length / numColumns);
79
- const rearranged = [];
80
- for (let row = 0; row < numRows; row++) {
81
- for (let col = 0; col < numColumns; col++) {
82
- const index = col * numRows + row;
83
- if (index < sortedList.length) {
84
- rearranged.push(sortedList[index]);
85
- }
86
- }
87
- }
88
- return rearranged.map((item, index: number) => (
89
- <li key={`item-list-2-${index}`}>
90
- <Link
91
- href={getLocationHref(item.code)}
92
- className="label1"
93
- >
94
- {item.name}
95
- </Link>
96
- </li>
97
- ));
98
- })()}
99
- </ul>
100
- <ul
101
- className={`hidden ${image ? "gap-x-6" : "gap-x-20"} gap-y-5 md:grid ${image ? "md:grid-cols-3" : "md:grid-cols-4"}`}
102
- >
103
- {(() => {
104
- const sortedList = [...list].sort((a, b) =>
105
- a.name.localeCompare(b.name)
106
- );
107
- const numColumns = 3;
108
- const numRows = Math.ceil(sortedList.length / numColumns);
109
- const rearranged = [];
110
- for (let row = 0; row < numRows; row++) {
111
- for (let col = 0; col < numColumns; col++) {
112
- const index = col * numRows + row;
113
- if (index < sortedList.length) {
114
- rearranged.push(sortedList[index]);
115
- }
116
- }
117
- }
118
- return rearranged.map((item, index: number) => (
119
- <li
120
- key={`item-list-3-${index}`}
121
- className={`${image ? "" : "w-[172px]"}`}
122
- >
123
- <Link
124
- href={getLocationHref(item.code)}
125
- className="label1"
126
- >
127
- {item.name}
128
- </Link>
129
- </li>
130
- ));
131
- })()}
132
- </ul>
133
- </>
134
- )}
135
- </div>
136
- {image && (
137
- <aside className="hidden md:block">
138
- <NextImage width={472} height={100} src={image} alt="image" />
139
- </aside>
140
- )}
141
- </div>
142
- </div>
143
- </div>
144
- );
145
- };
146
-
147
- export default FindKinetic;
1
+ import React from "react";
2
+ import { FindKineticProps, ThemeKey } from "./types";
3
+
4
+ import { Link } from "@shared/components/link";
5
+ import { Text } from "@shared/components/text";
6
+ import { NextImage } from "@shared/next";
7
+
8
+ export const FindKinetic: React.FC<FindKineticProps> = ({
9
+ background = "white",
10
+ description,
11
+ enableHeading,
12
+ image,
13
+ list = [],
14
+ localPathPrefix = "/local",
15
+ subTitle,
16
+ title,
17
+ color = "dark",
18
+ maxWidth = true,
19
+ }) => {
20
+ const bgColorClasses: Record<ThemeKey, string> = {
21
+ blue: "bg-[#07B2E2]",
22
+ green: "bg-[#26B170]",
23
+ yellow: "bg-[#F5FF1E]",
24
+ purple: "bg-[#931D69]",
25
+ white: "bg-white",
26
+ navy: "bg-[#00002D]",
27
+ };
28
+ const normalizedLocalPathPrefix = localPathPrefix
29
+ ? `/${localPathPrefix.replace(/^\/+|\/+$/g, "")}`
30
+ : "";
31
+
32
+ const getLocationHref = (code: string) =>
33
+ `${normalizedLocalPathPrefix}/${code.toLowerCase()}`;
34
+
35
+ return (
36
+ <div
37
+ className={`${bgColorClasses[background]} component-container overflow-hidden ${color == "dark" ? "text-text" : "text-white"}`}
38
+ >
39
+ <div
40
+ className={`${maxWidth ? "max-w-120 xl:mx-auto" : ""} mx-5 mb-5 ${image ? "mt-12" : "mt-0 xl:mt-0"} xl:my-20`}
41
+ >
42
+ {title && (
43
+ <Text
44
+ as={enableHeading ? "h1" : "h2"}
45
+ className="heading2 md:heading1 md:text-center"
46
+ >
47
+ {title}
48
+ </Text>
49
+ )}
50
+ {subTitle && (
51
+ <Text
52
+ as={enableHeading ? "h2" : "h3"}
53
+ className="subheading1 mt-4 md:text-center"
54
+ >
55
+ {subTitle}
56
+ </Text>
57
+ )}
58
+
59
+ <div
60
+ className={`mt-8 flex flex-col items-center xl:flex-row ${image ? "xl:mt-16" : "xl:mt-10"} gap-10`}
61
+ >
62
+ <div
63
+ className={`flex flex-col ${image ? "xl:items-start" : "items-center"} gap-10 self-stretch xl:flex-[1_0_0]`}
64
+ >
65
+ {description && (
66
+ <Text as="p" className="body1">
67
+ {description}
68
+ </Text>
69
+ )}
70
+ {list.length > 0 && (
71
+ <>
72
+ <ul className="grid w-full grid-cols-2 gap-x-6 gap-y-5 md:hidden">
73
+ {(() => {
74
+ const sortedList = [...list].sort((a, b) =>
75
+ a.name.localeCompare(b.name)
76
+ );
77
+ const numColumns = 2;
78
+ const numRows = Math.ceil(sortedList.length / numColumns);
79
+ const rearranged = [];
80
+ for (let row = 0; row < numRows; row++) {
81
+ for (let col = 0; col < numColumns; col++) {
82
+ const index = col * numRows + row;
83
+ if (index < sortedList.length) {
84
+ rearranged.push(sortedList[index]);
85
+ }
86
+ }
87
+ }
88
+ return rearranged.map((item, index: number) => (
89
+ <li key={`item-list-2-${index}`}>
90
+ <Link
91
+ href={getLocationHref(item.code)}
92
+ className="label1"
93
+ >
94
+ {item.name}
95
+ </Link>
96
+ </li>
97
+ ));
98
+ })()}
99
+ </ul>
100
+ <ul
101
+ className={`hidden ${image ? "gap-x-6" : "gap-x-20"} gap-y-5 md:grid ${image ? "md:grid-cols-3" : "md:grid-cols-4"}`}
102
+ >
103
+ {(() => {
104
+ const sortedList = [...list].sort((a, b) =>
105
+ a.name.localeCompare(b.name)
106
+ );
107
+ const numColumns = 3;
108
+ const numRows = Math.ceil(sortedList.length / numColumns);
109
+ const rearranged = [];
110
+ for (let row = 0; row < numRows; row++) {
111
+ for (let col = 0; col < numColumns; col++) {
112
+ const index = col * numRows + row;
113
+ if (index < sortedList.length) {
114
+ rearranged.push(sortedList[index]);
115
+ }
116
+ }
117
+ }
118
+ return rearranged.map((item, index: number) => (
119
+ <li
120
+ key={`item-list-3-${index}`}
121
+ className={`${image ? "" : "w-[172px]"}`}
122
+ >
123
+ <Link
124
+ href={getLocationHref(item.code)}
125
+ className="label1"
126
+ >
127
+ {item.name}
128
+ </Link>
129
+ </li>
130
+ ));
131
+ })()}
132
+ </ul>
133
+ </>
134
+ )}
135
+ </div>
136
+ {image && (
137
+ <aside className="hidden md:block">
138
+ <NextImage width={472} height={100} src={image} alt="image" />
139
+ </aside>
140
+ )}
141
+ </div>
142
+ </div>
143
+ </div>
144
+ );
145
+ };
146
+
147
+ export default FindKinetic;