@windstream/react-shared-components 0.1.74 → 0.1.76

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.
@@ -36,129 +36,213 @@ export const Navigation: React.FC<NavigationProps> = props => {
36
36
  primaryNavigationLogoWidth = 76.5,
37
37
  primaryNavigationLogoHeight = 24,
38
38
  hideMobileCallButton = false,
39
+ displayUtilityNavigation = true,
40
+ displaySearchBar = true,
41
+ displayCallNowCta = true,
42
+ showCallButton = true,
43
+ showCallNowCtaInMainNav = false,
44
+ showMobileSliderMenu = true,
45
+ callNowCtaIcon,
46
+ navigationBackgroundColor,
47
+ navigationLinkColor,
39
48
  } = props;
49
+
50
+ const logoUrl =
51
+ typeof primaryNavigationLogo === "string"
52
+ ? primaryNavigationLogo
53
+ : primaryNavigationLogo?.url || "";
54
+
55
+ // Background token convention matches the legacy ResidentialNavbar:
56
+ // when CMS provides a token like "navy500" we map it to
57
+ // `bg-secondary-navy500`. Raw Tailwind classes (containing a `-`)
58
+ // are passed through untouched. Defaults to `bg-secondary-navy500`
59
+ // to preserve legacy behavior when CMS leaves the field empty.
60
+ const mainNavBgClass = navigationBackgroundColor
61
+ ? navigationBackgroundColor.includes("-") ||
62
+ navigationBackgroundColor.startsWith("bg-")
63
+ ? navigationBackgroundColor
64
+ : `bg-secondary-${navigationBackgroundColor}`
65
+ : "bg-secondary-navy500";
66
+
67
+ const callNowCtaIconUrl =
68
+ typeof callNowCtaIcon === "string"
69
+ ? callNowCtaIcon
70
+ : callNowCtaIcon?.url || "";
71
+
72
+ // Styled "Call Now" pill used in the main nav rows when CMS opts in via
73
+ // `showCallNowCtaInMainNav` (desktop) or when the mobile slider menu is
74
+ // disabled (`showMobileSliderMenu: false`). Mirrors the legacy
75
+ // ResidentialNavbar styling so existing CMS entries render identically.
76
+ const renderMainNavCallCta = (
77
+ onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void
78
+ ) => (
79
+ <Link
80
+ href={invocaPhoneNumberLink || "#"}
81
+ onClick={onClick}
82
+ className="bg-secondary-gray100 text-secondary-navy500 inline-flex h-8 items-center rounded-full pl-1 pr-2 no-underline shadow-none"
83
+ aria-label={
84
+ invocaPhoneNumberDisplayText
85
+ ? `Call ${invocaPhoneNumberDisplayText}`
86
+ : "Call now"
87
+ }
88
+ >
89
+ {callNowCtaIconUrl ? (
90
+ <NextImage
91
+ src={callNowCtaIconUrl}
92
+ alt="Call us"
93
+ width={24}
94
+ height={24}
95
+ className="mr-2 self-center rounded-full"
96
+ />
97
+ ) : null}
98
+ <Text as="span" className="text-secondary-navy500 footnote">
99
+ {invocaPhoneNumberDisplayText}
100
+ </Text>
101
+ </Link>
102
+ );
103
+
104
+ const hasMobileMenuItems =
105
+ (primaryNavigationLinks?.length ?? 0) > 0 ||
106
+ (supportNavigationLinks?.length ?? 0) > 0 ||
107
+ (accountNavigationLinks?.length ?? 0) > 0;
108
+
40
109
  return (
41
110
  <div className="component-container">
42
111
  <nav className={`menu-container z-[1000]`}>
43
- <div className="utility-container hidden lg:block lg:border-b lg:px-2">
44
- <div className="mx-auto flex max-w-120 justify-between">
45
- <ul className="flex gap-5" aria-label="Utility Navigation">
46
- {utilityNavigationLinks?.map((links, index) => {
47
- return (
48
- <li key={`main-menu-items-${index}`}>
49
- <ContentfulButton
50
- linkClassName={cx(
51
- "footnote flex items-center w-full h-11 text-text",
52
- // If utilityNavActiveIndex is not provided, default to making the second link active (for existing business app). If utilityNavActiveIndex is provided, use it to determine which link is active.
53
- typeof utilityNavActiveIndex !== "number"
54
- ? index === 1 && "label4"
55
- : utilityNavActiveIndex === index && "label4"
56
- )}
57
- linkVariant="unstyled"
58
- {...(Object.fromEntries(
59
- Object.entries(links).filter(([_, v]) => v !== null)
60
- ) as any)}
112
+ {displayUtilityNavigation ? (
113
+ <div className="utility-container hidden lg:block lg:border-b lg:px-2">
114
+ <div className="mx-auto flex max-w-120 justify-between">
115
+ <ul className="flex gap-5" aria-label="Utility Navigation">
116
+ {utilityNavigationLinks?.map((links, index) => {
117
+ return (
118
+ <li key={`main-menu-items-${index}`}>
119
+ <ContentfulButton
120
+ linkClassName={cx(
121
+ "footnote flex items-center w-full h-11 text-text",
122
+ // If utilityNavActiveIndex is not provided, default to making the second link active (for existing business app). If utilityNavActiveIndex is provided, use it to determine which link is active.
123
+ typeof utilityNavActiveIndex !== "number"
124
+ ? index === 1 && "label4"
125
+ : utilityNavActiveIndex === index && "label4"
126
+ )}
127
+ linkVariant="unstyled"
128
+ {...(Object.fromEntries(
129
+ Object.entries(links).filter(([_, v]) => v !== null)
130
+ ) as any)}
131
+ />
132
+ </li>
133
+ );
134
+ })}
135
+ </ul>
136
+ <div className="flex items-center gap-10">
137
+ {displayCallNowCta ? (
138
+ <CallButton
139
+ className="border-none"
140
+ href={invocaPhoneNumberLink}
141
+ prefix={callNowCtaText}
142
+ onClick={onCallClickDesktop}
143
+ >
144
+ <Text className="body3 text-text">
145
+ {invocaPhoneNumberDisplayText}
146
+ </Text>
147
+ </CallButton>
148
+ ) : null}
149
+ {displayCartIcon ? (
150
+ <Link
151
+ href={cartHref || "#"}
152
+ className="relative inline-flex cursor-pointer"
153
+ aria-label={cartIconAriaLabel}
154
+ onClick={onCartClick}
155
+ >
156
+ <MaterialIcon name="shopping_cart" />
157
+ {cartHasRetention ? (
158
+ <span className="absolute -right-2 -top-1 h-2.5 w-2.5 rounded-full bg-icon-brand" />
159
+ ) : null}
160
+ </Link>
161
+ ) : null}
162
+ {accountNavigationLinks?.map((links, index) => {
163
+ return (
164
+ <DesktopLinkGroups
165
+ key={`my-account-${index}`}
166
+ anchorName={`my-account-${index}`}
167
+ link={links}
61
168
  />
62
- </li>
63
- );
64
- })}
65
- </ul>
66
- <div className="flex items-center gap-10">
67
- <CallButton
68
- className="border-none"
69
- href={invocaPhoneNumberLink}
70
- prefix={callNowCtaText}
71
- onClick={onCallClickDesktop}
72
- >
73
- <Text className="body3 text-text">
74
- {invocaPhoneNumberDisplayText}
75
- </Text>
76
- </CallButton>
77
- {displayCartIcon ? (
78
- <Link
79
- href={cartHref || "#"}
80
- className="relative inline-flex cursor-pointer"
81
- aria-label={cartIconAriaLabel}
82
- onClick={onCartClick}
83
- >
84
- <MaterialIcon name="shopping_cart" />
85
- {cartHasRetention ? (
86
- <span className="absolute -right-2 -top-1 h-2.5 w-2.5 rounded-full bg-icon-brand" />
87
- ) : null}
88
- </Link>
89
- ) : null}
90
- {accountNavigationLinks?.map((links, index) => {
91
- return (
92
- <DesktopLinkGroups
93
- key={`my-account-${index}`}
94
- anchorName={`my-account-${index}`}
95
- link={links}
96
- />
97
- );
98
- })}
169
+ );
170
+ })}
171
+ </div>
99
172
  </div>
100
173
  </div>
101
- </div>
102
- <div className="main-nav-container" aria-label="Main Navigation">
174
+ ) : null}
175
+ <div
176
+ className={cx(
177
+ "main-nav-container",
178
+ mainNavBgClass,
179
+ navigationLinkColor
180
+ )}
181
+ aria-label="Main Navigation"
182
+ >
103
183
  <div className="mobile-nav-section flex h-14 items-center justify-between border border-b px-5 py-[10px] lg:hidden">
104
184
  <div>
105
- <Link href="/" className="flex">
106
- <NextImage
107
- src={
108
- typeof primaryNavigationLogo === "string"
109
- ? primaryNavigationLogo
110
- : primaryNavigationLogo?.url || ""
111
- }
112
- alt="Kinetic business logo"
113
- width={primaryNavigationLogoWidth}
114
- height={primaryNavigationLogoHeight}
115
- />
116
- </Link>
185
+ {logoUrl ? (
186
+ <Link href="/" className="flex">
187
+ <NextImage
188
+ src={logoUrl}
189
+ alt="Kinetic business logo"
190
+ width={primaryNavigationLogoWidth}
191
+ height={primaryNavigationLogoHeight}
192
+ />
193
+ </Link>
194
+ ) : null}
117
195
  </div>
118
196
  <div className="flex items-center gap-6">
119
- {hideMobileCallButton ? null : (
120
- <CallButton
121
- href={invocaPhoneNumberLink}
122
- onClick={onCallClickMobile}
123
- >
124
- <Text as="span" className="footnote">
125
- {invocaPhoneNumberDisplayText}
126
- </Text>
127
- </CallButton>
128
- )}
129
- {displayCartIcon ? (
130
- <Link
131
- href={cartHref || "#"}
132
- className="relative inline-flex cursor-pointer"
133
- aria-label={cartIconAriaLabel}
134
- onClick={onCartClick}
135
- >
136
- <MaterialIcon name="shopping_cart" />
137
- {cartHasRetention ? (
138
- <span className="absolute -right-2 -top-1 h-2.5 w-2.5 rounded-full bg-icon-brand" />
197
+ {!showMobileSliderMenu ? (
198
+ showCallButton ? (
199
+ renderMainNavCallCta(onCallClickMobile)
200
+ ) : null
201
+ ) : (
202
+ <>
203
+ {showCallButton && !hideMobileCallButton ? (
204
+ <CallButton
205
+ href={invocaPhoneNumberLink}
206
+ onClick={onCallClickMobile}
207
+ >
208
+ <Text as="span" className="footnote">
209
+ {invocaPhoneNumberDisplayText}
210
+ </Text>
211
+ </CallButton>
139
212
  ) : null}
140
- </Link>
141
- ) : null}
142
- <MobileMenu {...props} />
213
+ {displayCartIcon ? (
214
+ <Link
215
+ href={cartHref || "#"}
216
+ className="relative inline-flex cursor-pointer"
217
+ aria-label={cartIconAriaLabel}
218
+ onClick={onCartClick}
219
+ >
220
+ <MaterialIcon name="shopping_cart" />
221
+ {cartHasRetention ? (
222
+ <span className="absolute -right-2 -top-1 h-2.5 w-2.5 rounded-full bg-icon-brand" />
223
+ ) : null}
224
+ </Link>
225
+ ) : null}
226
+ {hasMobileMenuItems ? <MobileMenu {...props} /> : null}
227
+ </>
228
+ )}
143
229
  </div>
144
230
  </div>
145
231
 
146
232
  <div className="desktop-nav-section hidden lg:block lg:border-b lg:px-2">
147
233
  <div className="mx-auto flex h-14 max-w-120 items-center justify-between">
148
234
  <div className="flex h-full">
149
- <Link href="/" className="flex">
150
- <NextImage
151
- src={
152
- typeof primaryNavigationLogo === "string"
153
- ? primaryNavigationLogo
154
- : primaryNavigationLogo?.url || ""
155
- }
156
- alt="Kinetic business logo"
157
- width={primaryNavigationLogoWidth}
158
- height={primaryNavigationLogoHeight}
159
- className="mr-[64px]"
160
- />
161
- </Link>
235
+ {logoUrl ? (
236
+ <Link href="/" className="flex">
237
+ <NextImage
238
+ src={logoUrl}
239
+ alt="Kinetic business logo"
240
+ width={primaryNavigationLogoWidth}
241
+ height={primaryNavigationLogoHeight}
242
+ className="mr-[64px]"
243
+ />
244
+ </Link>
245
+ ) : null}
162
246
 
163
247
  <div className="flex h-full items-center gap-5">
164
248
  {primaryNavigationLinks?.map((links, index) => {
@@ -167,26 +251,33 @@ export const Navigation: React.FC<NavigationProps> = props => {
167
251
  key={`main-menu-${index}`}
168
252
  anchorName={`main-menu-${index}`}
169
253
  link={links}
254
+ linkColorClassName={navigationLinkColor}
170
255
  />
171
256
  );
172
257
  })}
173
258
  </div>
174
259
  </div>
175
260
  <div className="flex h-full items-center gap-10">
176
- <DesktopSearchInput
177
- searchBarIconURL={
178
- typeof searchBarIcon === "string"
179
- ? searchBarIcon
180
- : searchBarIcon?.url || ""
181
- }
182
- onSearch={onSearch}
183
- />
261
+ {showCallNowCtaInMainNav
262
+ ? renderMainNavCallCta(onCallClickDesktop)
263
+ : null}
264
+ {displaySearchBar ? (
265
+ <DesktopSearchInput
266
+ searchBarIconURL={
267
+ typeof searchBarIcon === "string"
268
+ ? searchBarIcon
269
+ : searchBarIcon?.url || ""
270
+ }
271
+ onSearch={onSearch}
272
+ />
273
+ ) : null}
184
274
  {supportNavigationLinks?.map((links, index) => {
185
275
  return (
186
276
  <DesktopLinkGroups
187
277
  key={`support-menu-${index}`}
188
278
  anchorName={`support-menu-${index}`}
189
279
  link={links}
280
+ linkColorClassName={navigationLinkColor}
190
281
  />
191
282
  );
192
283
  })}
@@ -207,6 +298,9 @@ const MobileMenu = (props: NavigationProps) => {
207
298
  utilityNavigationLinks,
208
299
  supportNavigationLinks,
209
300
  accountNavigationLinks,
301
+ showCallButton,
302
+ displayCallNowCta,
303
+ displaySearchBar,
210
304
  } = props;
211
305
  const [isOpen, setIsOpen] = React.useState(false);
212
306
 
@@ -274,7 +368,7 @@ const MobileMenu = (props: NavigationProps) => {
274
368
  <div
275
369
  className={cx(
276
370
  "fixed bottom-0 right-0 top-0",
277
- "z-[100] h-full bg-bg px-0 py-4",
371
+ "z-[100] h-full bg-bg px-0 py-4 text-text",
278
372
  "transition-all duration-300 ease-in-out",
279
373
  "block",
280
374
  isOpen ? "right-0" : "-right-96"
@@ -282,16 +376,25 @@ const MobileMenu = (props: NavigationProps) => {
282
376
  id="mobile-menu-overlay"
283
377
  >
284
378
  <div id="drawer-items" className="flex h-full flex-col gap-3">
285
- <div className="flex items-center justify-between px-4">
286
- <div>
287
- <CallButton
288
- className="border-none"
289
- href={props.invocaPhoneNumberLink}
290
- onClick={props.onCallClickMobile}
291
- >
292
- {props.invocaPhoneNumberDisplayText}
293
- </CallButton>
294
- </div>
379
+ <div
380
+ className={cx(
381
+ "flex items-center px-4",
382
+ showCallButton !== false || displayCallNowCta !== false
383
+ ? "justify-between"
384
+ : "justify-end"
385
+ )}
386
+ >
387
+ {showCallButton !== false || displayCallNowCta !== false ? (
388
+ <div>
389
+ <CallButton
390
+ className="border-none"
391
+ href={props.invocaPhoneNumberLink}
392
+ onClick={props.onCallClickMobile}
393
+ >
394
+ {props.invocaPhoneNumberDisplayText}
395
+ </CallButton>
396
+ </div>
397
+ ) : null}
295
398
  <div>
296
399
  <ContentfulButton
297
400
  showButtonAs="unstyled"
@@ -302,16 +405,18 @@ const MobileMenu = (props: NavigationProps) => {
302
405
  </ContentfulButton>
303
406
  </div>
304
407
  </div>
305
- <MobileSearchInput
306
- closeMenu={closeMenu}
307
- isMenuOpen={isOpen}
308
- searchBarIconURL={
309
- typeof props.searchBarIcon === "string"
310
- ? props.searchBarIcon
311
- : props.searchBarIcon?.url || ""
312
- }
313
- onSearch={props.onSearch || (() => {})}
314
- />
408
+ {displaySearchBar !== false ? (
409
+ <MobileSearchInput
410
+ closeMenu={closeMenu}
411
+ isMenuOpen={isOpen}
412
+ searchBarIconURL={
413
+ typeof props.searchBarIcon === "string"
414
+ ? props.searchBarIcon
415
+ : props.searchBarIcon?.url || ""
416
+ }
417
+ onSearch={props.onSearch || (() => {})}
418
+ />
419
+ ) : null}
315
420
  <div
316
421
  className="flex-grow overflow-y-auto"
317
422
  onClick={event => {
@@ -22,6 +22,13 @@ export interface Navigation {
22
22
  navigationBackgroundColor?: Color;
23
23
  utilityNavBackgroundColor?: Color;
24
24
  utilityNavLinkColor?: Color;
25
+ /**
26
+ * Tailwind class (or raw class string) applied to the main nav row's
27
+ * text color, e.g. `"text-white"` or `"text-secondary-navy500"`. Used
28
+ * by section / secondary navbars rendered on a dark background where
29
+ * the labels need to be inverted.
30
+ */
31
+ navigationLinkColor?: string;
25
32
  utilityNavActiveIndex?: number;
26
33
 
27
34
  callNowCtaText?: string;