@windstream/react-shared-components 0.1.45 → 0.1.47

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.
Files changed (200) hide show
  1. package/README.md +635 -635
  2. package/dist/contentful/index.d.ts +2 -2
  3. package/dist/contentful/index.esm.js +2 -4
  4. package/dist/contentful/index.esm.js.map +1 -1
  5. package/dist/contentful/index.js +2 -4
  6. package/dist/contentful/index.js.map +1 -1
  7. package/dist/core.d.ts +1 -1
  8. package/dist/index.d.ts +3 -3
  9. package/dist/index.esm.js +5 -7
  10. package/dist/index.esm.js.map +1 -1
  11. package/dist/index.js +5 -7
  12. package/dist/index.js.map +1 -1
  13. package/dist/next/index.esm.js +1 -3
  14. package/dist/next/index.esm.js.map +1 -1
  15. package/dist/next/index.js +1 -3
  16. package/dist/next/index.js.map +1 -1
  17. package/dist/styles.css +1 -1
  18. package/dist/utils/index.d.ts +2 -3
  19. package/dist/utils/index.esm.js +1 -1
  20. package/dist/utils/index.esm.js.map +1 -1
  21. package/dist/utils/index.js +1 -1
  22. package/dist/utils/index.js.map +1 -1
  23. package/package.json +185 -185
  24. package/src/components/accordion/Accordion.stories.tsx +230 -230
  25. package/src/components/accordion/index.tsx +70 -70
  26. package/src/components/accordion/types.ts +12 -12
  27. package/src/components/alert-card/AlertCard.stories.tsx +171 -171
  28. package/src/components/alert-card/index.tsx +41 -41
  29. package/src/components/alert-card/types.ts +13 -13
  30. package/src/components/brand-button/BrandButton.stories.tsx +223 -223
  31. package/src/components/brand-button/helpers.ts +35 -35
  32. package/src/components/brand-button/index.tsx +120 -120
  33. package/src/components/brand-button/types.ts +38 -38
  34. package/src/components/button/Button.stories.tsx +108 -108
  35. package/src/components/button/index.tsx +27 -27
  36. package/src/components/button/types.ts +14 -14
  37. package/src/components/call-button/CallButton.stories.tsx +324 -324
  38. package/src/components/call-button/index.tsx +86 -86
  39. package/src/components/call-button/types.ts +11 -11
  40. package/src/components/checkbox/Checkbox.stories.tsx +247 -247
  41. package/src/components/checkbox/index.tsx +197 -197
  42. package/src/components/checkbox/types.ts +27 -27
  43. package/src/components/checklist/Checklist.stories.tsx +150 -150
  44. package/src/components/checklist/index.tsx +61 -61
  45. package/src/components/checklist/types.ts +17 -17
  46. package/src/components/collapse/Collapse.stories.tsx +255 -255
  47. package/src/components/collapse/index.tsx +46 -46
  48. package/src/components/collapse/types.ts +6 -6
  49. package/src/components/divider/Divider.stories.tsx +205 -205
  50. package/src/components/divider/index.tsx +22 -22
  51. package/src/components/divider/type.ts +3 -3
  52. package/src/components/image/Image.stories.tsx +113 -113
  53. package/src/components/image/index.tsx +25 -25
  54. package/src/components/image/types.ts +40 -40
  55. package/src/components/input/Input.stories.tsx +325 -325
  56. package/src/components/input/index.tsx +177 -177
  57. package/src/components/input/types.ts +37 -37
  58. package/src/components/link/Link.stories.tsx +163 -163
  59. package/src/components/link/index.tsx +109 -109
  60. package/src/components/link/types.ts +25 -25
  61. package/src/components/list/List.stories.tsx +272 -272
  62. package/src/components/list/index.tsx +88 -88
  63. package/src/components/list/list-item/index.tsx +38 -38
  64. package/src/components/list/list-item/types.ts +13 -13
  65. package/src/components/list/types.ts +29 -29
  66. package/src/components/material-icon/MaterialIcon.stories.tsx +322 -322
  67. package/src/components/material-icon/constants.ts +98 -98
  68. package/src/components/material-icon/index.tsx +47 -47
  69. package/src/components/material-icon/types.ts +31 -31
  70. package/src/components/modal/Modal.stories.tsx +171 -171
  71. package/src/components/modal/index.tsx +164 -164
  72. package/src/components/modal/types.ts +24 -24
  73. package/src/components/next-image/index.tsx +54 -54
  74. package/src/components/next-image/types.ts +1 -1
  75. package/src/components/pagination/index.tsx +100 -100
  76. package/src/components/pagination/types.ts +6 -6
  77. package/src/components/radio-button/RadioButton.stories.tsx +307 -307
  78. package/src/components/radio-button/index.tsx +75 -75
  79. package/src/components/radio-button/types.ts +21 -21
  80. package/src/components/see-more/SeeMore.stories.tsx +181 -181
  81. package/src/components/see-more/index.tsx +44 -44
  82. package/src/components/see-more/types.ts +4 -4
  83. package/src/components/select/Select.stories.tsx +411 -411
  84. package/src/components/select/index.tsx +155 -155
  85. package/src/components/select/types.ts +36 -36
  86. package/src/components/select-plan-button/SelectPlanButton.stories.tsx +184 -184
  87. package/src/components/select-plan-button/index.tsx +63 -63
  88. package/src/components/select-plan-button/types.ts +17 -17
  89. package/src/components/skeleton/Skeleton.stories.tsx +179 -179
  90. package/src/components/skeleton/index.tsx +61 -61
  91. package/src/components/skeleton/types.ts +4 -4
  92. package/src/components/spinner/Spinner.stories.tsx +335 -335
  93. package/src/components/spinner/index.tsx +44 -44
  94. package/src/components/spinner/types.ts +5 -5
  95. package/src/components/text/Text.stories.tsx +321 -321
  96. package/src/components/text/index.tsx +25 -25
  97. package/src/components/text/types.ts +45 -45
  98. package/src/components/tooltip/Tooltip.stories.tsx +219 -219
  99. package/src/components/tooltip/index.tsx +74 -74
  100. package/src/components/tooltip/types.ts +7 -7
  101. package/src/components/view-cart-button/ViewCartButton.stories.tsx +252 -252
  102. package/src/components/view-cart-button/index.tsx +42 -42
  103. package/src/components/view-cart-button/types.ts +5 -5
  104. package/src/contentful/blocks/accordion/Accordion.stories.tsx +34 -34
  105. package/src/contentful/blocks/accordion/index.tsx +112 -112
  106. package/src/contentful/blocks/accordion/types.ts +34 -34
  107. package/src/contentful/blocks/address-input-banner/index.tsx +52 -52
  108. package/src/contentful/blocks/address-input-banner/types.ts +14 -14
  109. package/src/contentful/blocks/anchored-bottom-banner/index.tsx +70 -70
  110. package/src/contentful/blocks/anchored-bottom-banner/types.ts +10 -10
  111. package/src/contentful/blocks/blogs-grid/index.tsx +134 -134
  112. package/src/contentful/blocks/blogs-grid/types.ts +26 -26
  113. package/src/contentful/blocks/breadcrumbs/index.tsx +51 -51
  114. package/src/contentful/blocks/breadcrumbs/types.ts +5 -5
  115. package/src/contentful/blocks/button/Button.stories.tsx +40 -40
  116. package/src/contentful/blocks/button/index.tsx +130 -130
  117. package/src/contentful/blocks/button/types.ts +39 -39
  118. package/src/contentful/blocks/callout/Callout.stories.tsx +23 -23
  119. package/src/contentful/blocks/callout/index.tsx +88 -88
  120. package/src/contentful/blocks/callout/types.ts +15 -15
  121. package/src/contentful/blocks/cards/Cards.stories.tsx +23 -23
  122. package/src/contentful/blocks/cards/blog-card/index.tsx +110 -110
  123. package/src/contentful/blocks/cards/blog-card/types.ts +18 -18
  124. package/src/contentful/blocks/cards/index.tsx +13 -13
  125. package/src/contentful/blocks/cards/product-card/index.tsx +251 -251
  126. package/src/contentful/blocks/cards/product-card/types.ts +28 -28
  127. package/src/contentful/blocks/cards/simple-card/index.tsx +89 -89
  128. package/src/contentful/blocks/cards/simple-card/types.ts +28 -28
  129. package/src/contentful/blocks/cards/testimonial-card/index.tsx +90 -90
  130. package/src/contentful/blocks/cards/testimonial-card/types.tsx +12 -12
  131. package/src/contentful/blocks/cards/types.ts +1 -1
  132. package/src/contentful/blocks/carousel/Carousel.stories.tsx +23 -23
  133. package/src/contentful/blocks/carousel/helper.tsx +440 -440
  134. package/src/contentful/blocks/carousel/index.tsx +85 -85
  135. package/src/contentful/blocks/carousel/types.ts +144 -144
  136. package/src/contentful/blocks/cartretentionbanner/CartRetentionBanner.tsx +99 -0
  137. package/src/contentful/blocks/cartretentionbanner/types.ts +11 -0
  138. package/src/contentful/blocks/comparison-table/index.tsx +27 -27
  139. package/src/contentful/blocks/comparison-table/types.ts +6 -6
  140. package/src/contentful/blocks/cookiebanner/index.tsx +146 -146
  141. package/src/contentful/blocks/cookiebanner/type.ts +7 -7
  142. package/src/contentful/blocks/cta-callout/CtaCallout.stories.tsx +46 -46
  143. package/src/contentful/blocks/cta-callout/index.tsx +71 -71
  144. package/src/contentful/blocks/cta-callout/types.ts +26 -26
  145. package/src/contentful/blocks/dynamic-tabs/index.tsx +204 -204
  146. package/src/contentful/blocks/dynamic-tabs/types.ts +21 -21
  147. package/src/contentful/blocks/email-input-block/index.tsx +117 -117
  148. package/src/contentful/blocks/email-input-block/types.ts +16 -16
  149. package/src/contentful/blocks/find-kinetic/FindKinetic.stories.tsx +23 -23
  150. package/src/contentful/blocks/find-kinetic/index.tsx +130 -130
  151. package/src/contentful/blocks/find-kinetic/types.ts +19 -19
  152. package/src/contentful/blocks/floating-banner/FloatingBanner.stories.tsx +34 -34
  153. package/src/contentful/blocks/floating-banner/index.tsx +97 -97
  154. package/src/contentful/blocks/floating-banner/types.ts +22 -22
  155. package/src/contentful/blocks/footer/Footer.stories.tsx +30 -30
  156. package/src/contentful/blocks/footer/index.tsx +91 -91
  157. package/src/contentful/blocks/footer/types.ts +13 -13
  158. package/src/contentful/blocks/image-promo-bar/ImagePromoBar.stories.tsx +23 -23
  159. package/src/contentful/blocks/image-promo-bar/helper.tsx +28 -28
  160. package/src/contentful/blocks/image-promo-bar/index.tsx +246 -246
  161. package/src/contentful/blocks/image-promo-bar/types.ts +44 -44
  162. package/src/contentful/blocks/image-promo-bar/vimeo-embed.tsx +93 -93
  163. package/src/contentful/blocks/image-promo-bar/youtube-embed.tsx +46 -46
  164. package/src/contentful/blocks/modal/constants.ts +53 -53
  165. package/src/contentful/blocks/modal/index.tsx +107 -107
  166. package/src/contentful/blocks/modal/types.ts +12 -12
  167. package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.tsx +113 -113
  168. package/src/contentful/blocks/navigation/index.tsx +394 -394
  169. package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.tsx +82 -82
  170. package/src/contentful/blocks/navigation/types.ts +41 -41
  171. package/src/contentful/blocks/primary-hero/PrimaryHero.stories.tsx +23 -23
  172. package/src/contentful/blocks/primary-hero/index.tsx +236 -236
  173. package/src/contentful/blocks/primary-hero/types.ts +37 -37
  174. package/src/contentful/blocks/search-block/index.tsx +90 -90
  175. package/src/contentful/blocks/search-block/types.ts +15 -15
  176. package/src/contentful/blocks/shape-background-wrapper/ShapeBackgroundWrapper.stories.tsx +26 -26
  177. package/src/contentful/blocks/shape-background-wrapper/index.tsx +124 -124
  178. package/src/contentful/blocks/shape-background-wrapper/types.ts +36 -36
  179. package/src/contentful/blocks/text/Text.stories.tsx +23 -23
  180. package/src/contentful/blocks/text/index.tsx +12 -12
  181. package/src/contentful/blocks/text/types.ts +1 -1
  182. package/src/contentful/index.ts +99 -99
  183. package/src/hooks/contentful/use-contentful-rich-text.tsx +310 -310
  184. package/src/hooks/contentful/use-processed-check-list.ts +63 -63
  185. package/src/hooks/use-body-scroll-lock.ts +34 -34
  186. package/src/hooks/use-carousel-swipe.ts +264 -264
  187. package/src/hooks/use-outside-click.ts +17 -17
  188. package/src/index.ts +101 -101
  189. package/src/next/index.ts +5 -5
  190. package/src/setupTests.ts +46 -46
  191. package/src/stories/DocsTemplate.tsx +24 -24
  192. package/src/styles/globals.css +343 -343
  193. package/src/types/global.d.ts +9 -9
  194. package/src/types/micro-components.ts +99 -99
  195. package/src/types/utm.ts +49 -49
  196. package/src/utils/contentful/to-document.ts +24 -24
  197. package/src/utils/cookie.ts +84 -84
  198. package/src/utils/cx.ts +49 -49
  199. package/src/utils/index.ts +38 -38
  200. package/src/utils/utm.ts +221 -221
@@ -1,70 +1,70 @@
1
- import React from "react";
2
- import { AnchoredBottomBannerProps } from "./types";
3
- import Link from "next/link";
4
-
5
- import { MaterialIcon } from "@shared/components/material-icon";
6
-
7
- export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
8
- ctaSuffixText,
9
- backgroundColor,
10
- iconName,
11
- boxShadow,
12
- ctaButtonLabel,
13
- ctaButtonLink,
14
- ctaButtonTarget,
15
- anchorId = "anchored-banner",
16
- }) => {
17
- const backGroundColorClasses = {
18
- navy: "bg-bg-fill-inverse",
19
- green: "bg-bg-fill-success",
20
- blue: "bg-bg-fill-brand-supporting",
21
- purple: "bg-bg-fill-brand-tertiary",
22
- yellow: "bg-bg-fill-brand-accent",
23
- };
24
-
25
- const bgClass = backgroundColor
26
- ? backGroundColorClasses[backgroundColor]
27
- : "bg-bg-fill-brand-accent";
28
-
29
- const isYellow = backgroundColor === "yellow" || !backgroundColor;
30
- const textColorClass = isYellow ? "text-text-primary" : "text-white";
31
-
32
- return (
33
- <section id={anchorId}>
34
- <div
35
- className={`fixed bottom-0 left-0 right-0 z-[30] flex w-full items-center justify-center px-4 py-3 transition-all duration-300 ${bgClass} ${
36
- boxShadow ? boxShadow : "shadow-[0_-4px_10px_rgba(0,0,0,0.1)]"
37
- }`}
38
- >
39
- <Link
40
- href={ctaButtonLink || "#"}
41
- target={ctaButtonTarget || "_self"}
42
- className="max-w-screen-xl w-full transition-all"
43
- >
44
- <div className="flex flex-col items-center justify-center gap-1 text-center font-black">
45
- <div
46
- className={`${textColorClass} break-words text-body1 font-black leading-snug md:text-[18px]`}
47
- >
48
- {iconName && (
49
- <MaterialIcon
50
- name={
51
- iconName as React.ComponentProps<
52
- typeof MaterialIcon
53
- >["name"]
54
- }
55
- size={24}
56
- fill={1}
57
- className={`${textColorClass} align-text-bottom`}
58
- />
59
- )}
60
- {ctaButtonLabel && ctaButtonLabel}{" "}
61
- {ctaSuffixText && <span className="ml-0.5">{ctaSuffixText}</span>}
62
- </div>
63
- </div>
64
- </Link>
65
- </div>
66
- </section>
67
- );
68
- };
69
-
70
- export default AnchoredBottomBanner;
1
+ import React from "react";
2
+ import { AnchoredBottomBannerProps } from "./types";
3
+ import Link from "next/link";
4
+
5
+ import { MaterialIcon } from "@shared/components/material-icon";
6
+
7
+ export const AnchoredBottomBanner: React.FC<AnchoredBottomBannerProps> = ({
8
+ ctaSuffixText,
9
+ backgroundColor,
10
+ iconName,
11
+ boxShadow,
12
+ ctaButtonLabel,
13
+ ctaButtonLink,
14
+ ctaButtonTarget,
15
+ anchorId = "anchored-banner",
16
+ }) => {
17
+ const backGroundColorClasses = {
18
+ navy: "bg-bg-fill-inverse",
19
+ green: "bg-bg-fill-success",
20
+ blue: "bg-bg-fill-brand-supporting",
21
+ purple: "bg-bg-fill-brand-tertiary",
22
+ yellow: "bg-bg-fill-brand-accent",
23
+ };
24
+
25
+ const bgClass = backgroundColor
26
+ ? backGroundColorClasses[backgroundColor]
27
+ : "bg-bg-fill-brand-accent";
28
+
29
+ const isYellow = backgroundColor === "yellow" || !backgroundColor;
30
+ const textColorClass = isYellow ? "text-text-primary" : "text-white";
31
+
32
+ return (
33
+ <section id={anchorId}>
34
+ <div
35
+ className={`fixed bottom-0 left-0 right-0 z-[30] flex w-full items-center justify-center px-4 py-3 transition-all duration-300 ${bgClass} ${
36
+ boxShadow ? boxShadow : "shadow-[0_-4px_10px_rgba(0,0,0,0.1)]"
37
+ }`}
38
+ >
39
+ <Link
40
+ href={ctaButtonLink || "#"}
41
+ target={ctaButtonTarget || "_self"}
42
+ className="max-w-screen-xl w-full transition-all"
43
+ >
44
+ <div className="flex flex-col items-center justify-center gap-1 text-center font-black">
45
+ <div
46
+ className={`${textColorClass} break-words text-body1 font-black leading-snug md:text-[18px]`}
47
+ >
48
+ {iconName && (
49
+ <MaterialIcon
50
+ name={
51
+ iconName as React.ComponentProps<
52
+ typeof MaterialIcon
53
+ >["name"]
54
+ }
55
+ size={24}
56
+ fill={1}
57
+ className={`${textColorClass} align-text-bottom`}
58
+ />
59
+ )}
60
+ {ctaButtonLabel && ctaButtonLabel}{" "}
61
+ {ctaSuffixText && <span className="ml-0.5">{ctaSuffixText}</span>}
62
+ </div>
63
+ </div>
64
+ </Link>
65
+ </div>
66
+ </section>
67
+ );
68
+ };
69
+
70
+ export default AnchoredBottomBanner;
@@ -1,10 +1,10 @@
1
- export interface AnchoredBottomBannerProps {
2
- ctaSuffixText?: string;
3
- backgroundColor?: "navy" | "yellow" | "green" | "purple" | "blue";
4
- iconName?: string;
5
- boxShadow?: boolean;
6
- ctaButtonLabel?: string;
7
- ctaButtonLink?: string;
8
- ctaButtonTarget?: string;
9
- anchorId?: string;
10
- }
1
+ export interface AnchoredBottomBannerProps {
2
+ ctaSuffixText?: string;
3
+ backgroundColor?: "navy" | "yellow" | "green" | "purple" | "blue";
4
+ iconName?: string;
5
+ boxShadow?: boolean;
6
+ ctaButtonLabel?: string;
7
+ ctaButtonLink?: string;
8
+ ctaButtonTarget?: string;
9
+ anchorId?: string;
10
+ }
@@ -1,134 +1,134 @@
1
- "use client";
2
-
3
- import { BlogCategoryOption, BlogGridProps } from "./types";
4
- import { usePathname, useRouter, useSearchParams } from "next/navigation";
5
-
6
- import { Pagination } from "@shared/components/pagination";
7
- import { Select } from "@shared/components/select";
8
- import { Text } from "@shared/components/text";
9
- import { BlogCard } from "@shared/contentful/blocks/cards/blog-card";
10
-
11
- export function BlogGrid({
12
- paginatedArticles,
13
- totalArticles,
14
- currentPage,
15
- totalPages,
16
- selectedCategory,
17
- categoryOptions,
18
- }: BlogGridProps) {
19
- const router = useRouter();
20
- const pathname = usePathname();
21
- const searchParams = useSearchParams();
22
-
23
- function updateQueryParams(key: string, value: string) {
24
- const params = new URLSearchParams(searchParams.toString());
25
- if (value) {
26
- params.set(key, value);
27
- } else {
28
- params.delete(key);
29
- }
30
- // Delete page when category changes
31
- if (key === "category") {
32
- params.delete("page");
33
- }
34
- router.push(`${pathname}?${params.toString()}`, { scroll: false });
35
- }
36
-
37
- function handleCategoryChange(value: unknown) {
38
- if (!value || Array.isArray(value)) return;
39
- const cat = value as BlogCategoryOption;
40
- updateQueryParams("category", cat.value === "all" ? "" : cat.value);
41
- }
42
-
43
- function handlePageChange(page: number) {
44
- updateQueryParams("page", page === 1 ? "" : page.toString());
45
- window.scrollTo({ top: 0, behavior: "smooth" });
46
- }
47
-
48
- return (
49
- <section
50
- aria-label="Blog articles"
51
- data-section-type={"blogs-grid"}
52
- data-section-index={"0"}
53
- >
54
- {/* Controls bar */}
55
- <div className="mx-auto flex max-w-[1200px] flex-wrap items-center justify-between gap-3 px-6 pb-6">
56
- <Text className="w-full text-center text-heading5 font-black lowercase text-text-secondary md:w-auto md:text-left">
57
- recent articles
58
- </Text>
59
-
60
- <div className="flex w-full flex-col-reverse gap-3 md:w-auto md:flex-row md:items-center md:gap-6">
61
- {/* Showing count */}
62
- <Text className="whitespace-nowrap text-center text-label3 font-bold text-text-info md:text-right md:text-label2">
63
- <Text as="span" className="hidden md:block">
64
- showing
65
- </Text>
66
- <strong className="block text-nowrap font-bold text-text-info">
67
- <Text as="span" className="md:hidden">
68
- showing{" "}
69
- </Text>
70
- {paginatedArticles.length} of {totalArticles} articles
71
- </strong>
72
- </Text>
73
-
74
- {/* Filter dropdown */}
75
- <Select
76
- options={categoryOptions}
77
- value={selectedCategory}
78
- onChange={handleCategoryChange}
79
- className="w-full md:w-[280px]"
80
- />
81
- </div>
82
- </div>
83
-
84
- {/* Articles grid */}
85
- {paginatedArticles.length > 0 ? (
86
- <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">
87
- {paginatedArticles.map((article, index) => {
88
- const href = article.slug.startsWith("/")
89
- ? article.slug
90
- : `/${article.slug}`;
91
- const coverImage = article?.cover?.url
92
- ? {
93
- src: article?.cover?.url,
94
- alt:
95
- article?.cover?.title ||
96
- article?.title ||
97
- "Blog article image",
98
- width: article?.cover?.width || 600,
99
- height: article?.cover?.height || 338,
100
- }
101
- : undefined;
102
-
103
- return (
104
- <BlogCard
105
- key={article.slug}
106
- href={href}
107
- title={article.title}
108
- description={article.shortDescription}
109
- date={article.blogCreationDate}
110
- category={article.category}
111
- image={coverImage}
112
- index={index}
113
- />
114
- );
115
- })}
116
- </div>
117
- ) : (
118
- <Text className="px-6 py-12 text-center text-text-info">
119
- No articles found.
120
- </Text>
121
- )}
122
-
123
- {/* Pagination */}
124
- {totalPages > 1 && (
125
- <Pagination
126
- currentPage={currentPage}
127
- totalPages={totalPages}
128
- onPageChange={handlePageChange}
129
- ariaLabel="Blog article pagination"
130
- />
131
- )}
132
- </section>
133
- );
134
- }
1
+ "use client";
2
+
3
+ import { BlogCategoryOption, BlogGridProps } from "./types";
4
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
5
+
6
+ import { Pagination } from "@shared/components/pagination";
7
+ import { Select } from "@shared/components/select";
8
+ import { Text } from "@shared/components/text";
9
+ import { BlogCard } from "@shared/contentful/blocks/cards/blog-card";
10
+
11
+ export function BlogGrid({
12
+ paginatedArticles,
13
+ totalArticles,
14
+ currentPage,
15
+ totalPages,
16
+ selectedCategory,
17
+ categoryOptions,
18
+ }: BlogGridProps) {
19
+ const router = useRouter();
20
+ const pathname = usePathname();
21
+ const searchParams = useSearchParams();
22
+
23
+ function updateQueryParams(key: string, value: string) {
24
+ const params = new URLSearchParams(searchParams.toString());
25
+ if (value) {
26
+ params.set(key, value);
27
+ } else {
28
+ params.delete(key);
29
+ }
30
+ // Delete page when category changes
31
+ if (key === "category") {
32
+ params.delete("page");
33
+ }
34
+ router.push(`${pathname}?${params.toString()}`, { scroll: false });
35
+ }
36
+
37
+ function handleCategoryChange(value: unknown) {
38
+ if (!value || Array.isArray(value)) return;
39
+ const cat = value as BlogCategoryOption;
40
+ updateQueryParams("category", cat.value === "all" ? "" : cat.value);
41
+ }
42
+
43
+ function handlePageChange(page: number) {
44
+ updateQueryParams("page", page === 1 ? "" : page.toString());
45
+ window.scrollTo({ top: 0, behavior: "smooth" });
46
+ }
47
+
48
+ return (
49
+ <section
50
+ aria-label="Blog articles"
51
+ data-section-type={"blogs-grid"}
52
+ data-section-index={"0"}
53
+ >
54
+ {/* Controls bar */}
55
+ <div className="mx-auto flex max-w-[1200px] flex-wrap items-center justify-between gap-3 px-6 pb-6">
56
+ <Text className="w-full text-center text-heading5 font-black lowercase text-text-secondary md:w-auto md:text-left">
57
+ recent articles
58
+ </Text>
59
+
60
+ <div className="flex w-full flex-col-reverse gap-3 md:w-auto md:flex-row md:items-center md:gap-6">
61
+ {/* Showing count */}
62
+ <Text className="whitespace-nowrap text-center text-label3 font-bold text-text-info md:text-right md:text-label2">
63
+ <Text as="span" className="hidden md:block">
64
+ showing
65
+ </Text>
66
+ <strong className="block text-nowrap font-bold text-text-info">
67
+ <Text as="span" className="md:hidden">
68
+ showing{" "}
69
+ </Text>
70
+ {paginatedArticles.length} of {totalArticles} articles
71
+ </strong>
72
+ </Text>
73
+
74
+ {/* Filter dropdown */}
75
+ <Select
76
+ options={categoryOptions}
77
+ value={selectedCategory}
78
+ onChange={handleCategoryChange}
79
+ className="w-full md:w-[280px]"
80
+ />
81
+ </div>
82
+ </div>
83
+
84
+ {/* Articles grid */}
85
+ {paginatedArticles.length > 0 ? (
86
+ <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">
87
+ {paginatedArticles.map((article, index) => {
88
+ const href = article.slug.startsWith("/")
89
+ ? article.slug
90
+ : `/${article.slug}`;
91
+ const coverImage = article?.cover?.url
92
+ ? {
93
+ src: article?.cover?.url,
94
+ alt:
95
+ article?.cover?.title ||
96
+ article?.title ||
97
+ "Blog article image",
98
+ width: article?.cover?.width || 600,
99
+ height: article?.cover?.height || 338,
100
+ }
101
+ : undefined;
102
+
103
+ return (
104
+ <BlogCard
105
+ key={article.slug}
106
+ href={href}
107
+ title={article.title}
108
+ description={article.shortDescription}
109
+ date={article.blogCreationDate}
110
+ category={article.category}
111
+ image={coverImage}
112
+ index={index}
113
+ />
114
+ );
115
+ })}
116
+ </div>
117
+ ) : (
118
+ <Text className="px-6 py-12 text-center text-text-info">
119
+ No articles found.
120
+ </Text>
121
+ )}
122
+
123
+ {/* Pagination */}
124
+ {totalPages > 1 && (
125
+ <Pagination
126
+ currentPage={currentPage}
127
+ totalPages={totalPages}
128
+ onPageChange={handlePageChange}
129
+ ariaLabel="Blog article pagination"
130
+ />
131
+ )}
132
+ </section>
133
+ );
134
+ }
@@ -1,26 +1,26 @@
1
- export interface BlogCardProps {
2
- slug: string;
3
- title?: string;
4
- shortDescription?: string;
5
- blogCreationDate?: string;
6
- category?: string;
7
- cover?: {
8
- url: string;
9
- width?: number;
10
- height?: number;
11
- title?: string;
12
- };
13
- }
14
- export interface BlogGridProps {
15
- paginatedArticles: BlogCardProps[];
16
- totalArticles: number;
17
- currentPage: number;
18
- totalPages: number;
19
- selectedCategory: BlogCategoryOption;
20
- categoryOptions: BlogCategoryOption[];
21
- }
22
-
23
- export interface BlogCategoryOption {
24
- value: string;
25
- label: string;
26
- }
1
+ export interface BlogCardProps {
2
+ slug: string;
3
+ title?: string;
4
+ shortDescription?: string;
5
+ blogCreationDate?: string;
6
+ category?: string;
7
+ cover?: {
8
+ url: string;
9
+ width?: number;
10
+ height?: number;
11
+ title?: string;
12
+ };
13
+ }
14
+ export interface BlogGridProps {
15
+ paginatedArticles: BlogCardProps[];
16
+ totalArticles: number;
17
+ currentPage: number;
18
+ totalPages: number;
19
+ selectedCategory: BlogCategoryOption;
20
+ categoryOptions: BlogCategoryOption[];
21
+ }
22
+
23
+ export interface BlogCategoryOption {
24
+ value: string;
25
+ label: string;
26
+ }
@@ -1,51 +1,51 @@
1
- import React from "react";
2
- import Button from "../button";
3
- import { BreadcrumbNavigationProps } from "./types";
4
-
5
- import { MaterialIcon } from "@shared/components/material-icon";
6
- import { Text } from "@shared/components/text";
7
-
8
- export const BreadcrumbNavigation: React.FC<
9
- BreadcrumbNavigationProps
10
- > = props => {
11
- const { links = [], textColor = "dark", maxWidth = true } = props;
12
- const color =
13
- textColor === "dark"
14
- ? "text-white xl:text-text"
15
- : "text-text xl:text-white";
16
- if (!links.length) return null;
17
- return (
18
- <nav
19
- aria-label="Breadcrumb"
20
- className={`${maxWidth ? "mx-5 max-w-120 xl:mx-auto" : "mx-auto"} relative`}
21
- >
22
- <ol
23
- className={
24
- "relative right-0 z-10 mt-4 flex w-full flex-nowrap items-center gap-2 xl:absolute xl:flex-wrap"
25
- }
26
- >
27
- {links.map((link, index) => {
28
- const isLast = index === links.length - 1;
29
- return !isLast ? (
30
- <li key={index} className="flex flex-none items-center">
31
- <Button
32
- {...link}
33
- linkClassName={`label3 mr-2 whitespace-nowrap ${color}`}
34
- linkVariant="unstyled"
35
- />
36
- <MaterialIcon name="chevron_right" className={`${color} `} />
37
- </li>
38
- ) : (
39
- <li key={index} className="flex min-w-0 items-center">
40
- <Text as="span" className={`body3 mr-2 break-words ${color}`}>
41
- {link.buttonLabel}
42
- </Text>
43
- </li>
44
- );
45
- })}
46
- </ol>
47
- </nav>
48
- );
49
- };
50
-
51
- export default BreadcrumbNavigation;
1
+ import React from "react";
2
+ import Button from "../button";
3
+ import { BreadcrumbNavigationProps } from "./types";
4
+
5
+ import { MaterialIcon } from "@shared/components/material-icon";
6
+ import { Text } from "@shared/components/text";
7
+
8
+ export const BreadcrumbNavigation: React.FC<
9
+ BreadcrumbNavigationProps
10
+ > = props => {
11
+ const { links = [], textColor = "dark", maxWidth = true } = props;
12
+ const color =
13
+ textColor === "dark"
14
+ ? "text-white xl:text-text"
15
+ : "text-text xl:text-white";
16
+ if (!links.length) return null;
17
+ return (
18
+ <nav
19
+ aria-label="Breadcrumb"
20
+ className={`${maxWidth ? "mx-5 max-w-120 xl:mx-auto" : "mx-auto"} relative`}
21
+ >
22
+ <ol
23
+ className={
24
+ "relative right-0 z-10 mt-4 flex w-full flex-nowrap items-center gap-2 xl:absolute xl:flex-wrap"
25
+ }
26
+ >
27
+ {links.map((link, index) => {
28
+ const isLast = index === links.length - 1;
29
+ return !isLast ? (
30
+ <li key={index} className="flex flex-none items-center">
31
+ <Button
32
+ {...link}
33
+ linkClassName={`label3 mr-2 whitespace-nowrap ${color}`}
34
+ linkVariant="unstyled"
35
+ />
36
+ <MaterialIcon name="chevron_right" className={`${color} `} />
37
+ </li>
38
+ ) : (
39
+ <li key={index} className="flex min-w-0 items-center">
40
+ <Text as="span" className={`body3 mr-2 break-words ${color}`}>
41
+ {link.buttonLabel}
42
+ </Text>
43
+ </li>
44
+ );
45
+ })}
46
+ </ol>
47
+ </nav>
48
+ );
49
+ };
50
+
51
+ export default BreadcrumbNavigation;
@@ -1,5 +1,5 @@
1
- export type BreadcrumbNavigationProps = {
2
- links?: Array<any>;
3
- textColor?: "dark" | "light";
4
- maxWidth?: boolean;
5
- };
1
+ export type BreadcrumbNavigationProps = {
2
+ links?: Array<any>;
3
+ textColor?: "dark" | "light";
4
+ maxWidth?: boolean;
5
+ };