@windstream/react-shared-components 0.2.80 → 0.2.81

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/dist/index.d.ts CHANGED
@@ -67,11 +67,11 @@ type InputFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size"> & {
67
67
 
68
68
  declare const InputField: ForwardRefRenderFunction<HTMLInputElement, InputFieldProps>;
69
69
  declare const Input: React$1.ForwardRefExoticComponent<Omit<React$1.InputHTMLAttributes<HTMLInputElement>, "size"> & {
70
- state?: "error" | "default" | "focus" | "active" | "hover" | "filled" | undefined;
71
- size?: "medium" | "slim" | "large" | undefined;
70
+ state?: "default" | "focus" | "active" | "hover" | "filled" | "error" | undefined;
71
+ size?: "slim" | "medium" | "large" | undefined;
72
72
  label?: string | undefined;
73
73
  errorText?: string | undefined;
74
- prefixIconName?: "search" | "location_on" | undefined;
74
+ prefixIconName?: "location_on" | "search" | undefined;
75
75
  prefixIconSize?: 20 | 24 | 40 | 48 | undefined;
76
76
  prefixIconFill?: boolean | undefined;
77
77
  suffixIconFill?: boolean | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windstream/react-shared-components",
3
- "version": "0.2.80",
3
+ "version": "0.2.81",
4
4
  "type": "module",
5
5
  "description": "Shared React components for Kinetic applications",
6
6
  "main": "dist/index.js",
@@ -14,6 +14,19 @@ describe("AddressInputBanner", () => {
14
14
  expect(screen.getByText("Check availability")).toBeInTheDocument();
15
15
  });
16
16
 
17
+ it("spreads title fieldProps onto the title element", () => {
18
+ render(
19
+ <AddressInputBanner
20
+ {...defaultProps}
21
+ fieldProps={{ title: { "data-contentful-field-id": "title" } }}
22
+ />
23
+ );
24
+ expect(screen.getByText("Check availability")).toHaveAttribute(
25
+ "data-contentful-field-id",
26
+ "title"
27
+ );
28
+ });
29
+
17
30
  it("returns null when isVisible is false", () => {
18
31
  const { container } = render(
19
32
  <AddressInputBanner {...defaultProps} isVisible={false} />
@@ -19,6 +19,7 @@ export const AddressInputBanner: FC<AddressInputBannerProps> = props => {
19
19
  navHeight,
20
20
  isVisible,
21
21
  renderCheckPlans,
22
+ fieldProps,
22
23
  } = props;
23
24
 
24
25
  const style = variantStyles[variant?.toLowerCase()] || variantStyles.yellow;
@@ -43,7 +44,10 @@ export const AddressInputBanner: FC<AddressInputBannerProps> = props => {
43
44
  "flex flex-col items-center justify-center gap-3 p-[10px] lg:flex-row lg:gap-8 lg:px-6 lg:py-[10px]"
44
45
  )}
45
46
  >
46
- <Text className="label3 w-full text-center md:label1 lg:w-auto lg:text-left">
47
+ <Text
48
+ className="label3 w-full text-center md:label1 lg:w-auto lg:text-left"
49
+ {...fieldProps?.title}
50
+ >
47
51
  {title}
48
52
  </Text>
49
53
  {renderedCheckPlans ? <div>{renderedCheckPlans}</div> : null}
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
 
3
3
  import { ButtonProps } from "@shared/contentful/blocks/button/types";
4
+ import type { FieldProps } from "@shared/types/field-props";
4
5
  import { CheckPlansProps } from "@shared/types/micro-components";
5
6
 
6
7
  export interface AddressInputBannerProps {
@@ -11,4 +12,6 @@ export interface AddressInputBannerProps {
11
12
  navHeight?: number;
12
13
  isVisible?: boolean;
13
14
  renderCheckPlans?: (overrides?: CheckPlansProps) => React.ReactNode;
15
+ /** Extra DOM attribute props, spread onto the respective rendered elements. */
16
+ fieldProps?: FieldProps<"title">;
14
17
  }
@@ -5,6 +5,10 @@ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
5
5
  import "@testing-library/jest-dom/jest-globals";
6
6
  import { fireEvent, render, screen } from "@testing-library/react";
7
7
 
8
+ // Keeps mocked DOM elements free of non-DOM props while still surfacing data-* attributes.
9
+ const dataAttrs = (rest: Record<string, unknown>) =>
10
+ Object.fromEntries(Object.entries(rest).filter(([k]) => k.startsWith("data-")));
11
+
8
12
  jest.mock("@shared/components/brand-button", () => ({
9
13
  BrandButton: ({
10
14
  as,
@@ -37,6 +41,7 @@ jest.mock("@shared/components/brand-button", () => ({
37
41
  data-icon-fill={iconFill}
38
42
  className={buttonClassName}
39
43
  onClick={onClick}
44
+ {...dataAttrs(rest)}
40
45
  >
41
46
  {label} {text}
42
47
  </Tag>
@@ -45,9 +50,13 @@ jest.mock("@shared/components/brand-button", () => ({
45
50
  }));
46
51
 
47
52
  jest.mock("@shared/components/button", () => ({
48
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
49
53
  Button: ({ onClick, className, children, ...rest }: any) => (
50
- <button data-testid="core-button" className={className} onClick={onClick}>
54
+ <button
55
+ data-testid="core-button"
56
+ className={className}
57
+ onClick={onClick}
58
+ {...dataAttrs(rest)}
59
+ >
51
60
  {children}
52
61
  </button>
53
62
  ),
@@ -63,6 +72,7 @@ jest.mock("@shared/components/link", () => ({
63
72
  className,
64
73
  title,
65
74
  onClick,
75
+ ...rest
66
76
  }: any) => (
67
77
  <a
68
78
  data-testid="link"
@@ -71,6 +81,7 @@ jest.mock("@shared/components/link", () => ({
71
81
  className={className}
72
82
  title={title}
73
83
  onClick={onClick}
84
+ {...dataAttrs(rest)}
74
85
  >
75
86
  {children}
76
87
  </a>
@@ -97,6 +108,38 @@ describe("Button", () => {
97
108
  jest.clearAllMocks();
98
109
  });
99
110
 
111
+ describe("fieldProps", () => {
112
+ const fieldProps = {
113
+ "data-contentful-entry-id": "entry-1",
114
+ "data-contentful-field-id": "title",
115
+ };
116
+
117
+ it.each([
118
+ ["solid", "brand-button"],
119
+ ["text", "link"],
120
+ ["unstyled", "core-button"],
121
+ ] as const)("spreads fieldProps onto the %s variant", (variant, testId) => {
122
+ render(
123
+ <Button
124
+ buttonLabel="Go"
125
+ href="/page"
126
+ showButtonAs={variant}
127
+ fieldProps={fieldProps}
128
+ />
129
+ );
130
+ const el = screen.getByTestId(testId);
131
+ expect(el).toHaveAttribute("data-contentful-entry-id", "entry-1");
132
+ expect(el).toHaveAttribute("data-contentful-field-id", "title");
133
+ });
134
+
135
+ it("renders no tagging attributes when fieldProps is omitted", () => {
136
+ render(<Button buttonLabel="Go" href="/page" showButtonAs="text" />);
137
+ expect(screen.getByTestId("link")).not.toHaveAttribute(
138
+ "data-contentful-entry-id"
139
+ );
140
+ });
141
+ });
142
+
100
143
  describe("showButtonAs='solid' (default)", () => {
101
144
  it("renders BrandButton as anchor when href is provided", () => {
102
145
  render(<Button buttonLabel="Click" href="/page" />);
@@ -50,6 +50,7 @@ export const Button: React.FC<ButtonProps> = props => {
50
50
  preserveQueryParameters,
51
51
  disableAnimation,
52
52
  animationType = DEFAULT_BUTTON_ANIMATION_TYPE,
53
+ fieldProps,
53
54
  ...rest
54
55
  } = props;
55
56
 
@@ -143,7 +144,12 @@ export const Button: React.FC<ButtonProps> = props => {
143
144
  ? buildPreservedQueryHref(href, currentSearch)
144
145
  : href;
145
146
  let CTA = (
146
- <CoreButton onClick={buttonClick} className={buttonClassName} {...rest} />
147
+ <CoreButton
148
+ onClick={buttonClick}
149
+ className={buttonClassName}
150
+ {...rest}
151
+ {...fieldProps}
152
+ />
147
153
  );
148
154
 
149
155
  switch (showButtonAs) {
@@ -166,6 +172,7 @@ export const Button: React.FC<ButtonProps> = props => {
166
172
  iconFill={iconFill}
167
173
  disableAnimation={disableAnimation}
168
174
  animationType={animationType}
175
+ {...fieldProps}
169
176
  />
170
177
  ) : (
171
178
  <BrandButton
@@ -183,6 +190,7 @@ export const Button: React.FC<ButtonProps> = props => {
183
190
  buttonClassName={brandButtonClassName}
184
191
  disableAnimation={disableAnimation}
185
192
  animationType={animationType}
193
+ {...fieldProps}
186
194
  />
187
195
  );
188
196
  break;
@@ -199,6 +207,7 @@ export const Button: React.FC<ButtonProps> = props => {
199
207
  linkClassName
200
208
  )}
201
209
  onClick={linkClick}
210
+ {...fieldProps}
202
211
  >
203
212
  {matIconName ? (
204
213
  <>
@@ -242,6 +251,7 @@ export const Button: React.FC<ButtonProps> = props => {
242
251
  iconFill={iconFill}
243
252
  disableAnimation={disableAnimation}
244
253
  animationType={animationType}
254
+ {...fieldProps}
245
255
  />
246
256
  ) : (
247
257
  <BrandButton
@@ -258,6 +268,7 @@ export const Button: React.FC<ButtonProps> = props => {
258
268
  iconFill={iconFill}
259
269
  disableAnimation={disableAnimation}
260
270
  animationType={animationType}
271
+ {...fieldProps}
261
272
  />
262
273
  );
263
274
  break;
@@ -287,6 +298,7 @@ export const Button: React.FC<ButtonProps> = props => {
287
298
  buttonClassName
288
299
  )}
289
300
  aria-label={buttonLabel}
301
+ {...fieldProps}
290
302
  >
291
303
  {isLeftIcon && matIconName ? (
292
304
  <MaterialIcon
@@ -45,4 +45,6 @@ export type ButtonProps = {
45
45
  iconSize?: Size;
46
46
  disableAnimation?: boolean;
47
47
  animationType?: AnimationType | AnimationType[];
48
+ /** Extra DOM attribute props, spread onto the rendered link/button element. */
49
+ fieldProps?: Record<string, unknown>;
48
50
  };
@@ -12,8 +12,15 @@ jest.mock("@shared/components/divider", () => ({
12
12
  }));
13
13
 
14
14
  jest.mock("@shared/components/text", () => ({
15
- Text: ({ as: Tag = "span", children, className }: any) => (
16
- <Tag className={className}>{children}</Tag>
15
+ Text: ({ as: Tag = "span", children, className, ...rest }: any) => (
16
+ <Tag
17
+ className={className}
18
+ {...Object.fromEntries(
19
+ Object.entries(rest).filter(([k]) => k.startsWith("data-"))
20
+ )}
21
+ >
22
+ {children}
23
+ </Tag>
17
24
  ),
18
25
  }));
19
26
 
@@ -74,6 +81,62 @@ const defaultProps: FooterProps = {
74
81
  };
75
82
 
76
83
  describe("Footer", () => {
84
+ describe("fieldProps", () => {
85
+ const tag = (field: string) => ({
86
+ "data-contentful-field-id": field,
87
+ });
88
+
89
+ it("spreads body, terms and copyrights fieldProps onto their elements", () => {
90
+ const { container } = render(
91
+ <Footer
92
+ {...defaultProps}
93
+ fieldProps={{
94
+ body: tag("body"),
95
+ terms: tag("termsText"),
96
+ copyrights: tag("copyrightText"),
97
+ }}
98
+ />
99
+ );
100
+ expect(
101
+ screen.getByLabelText("Promotions and disclaimers")
102
+ ).toHaveAttribute("data-contentful-field-id", "body");
103
+ expect(container.querySelector(".terms-text")).toHaveAttribute(
104
+ "data-contentful-field-id",
105
+ "termsText"
106
+ );
107
+ expect(container.querySelector(".copy-rights")).toHaveAttribute(
108
+ "data-contentful-field-id",
109
+ "copyrightText"
110
+ );
111
+ });
112
+
113
+ it("spreads a link group's title fieldProps onto the group title", () => {
114
+ render(
115
+ <Footer
116
+ {...defaultProps}
117
+ links={[
118
+ { ...mockLinks[0], fieldProps: { title: tag("title") } },
119
+ mockLinks[1],
120
+ ]}
121
+ />
122
+ );
123
+ expect(screen.getByText("Products")).toHaveAttribute(
124
+ "data-contentful-field-id",
125
+ "title"
126
+ );
127
+ expect(screen.getByText("Support")).not.toHaveAttribute(
128
+ "data-contentful-field-id"
129
+ );
130
+ });
131
+
132
+ it("renders no tagging attributes when fieldProps is omitted", () => {
133
+ const { container } = render(<Footer {...defaultProps} />);
134
+ expect(
135
+ container.querySelector("[data-contentful-field-id]")
136
+ ).toBeNull();
137
+ });
138
+ });
139
+
77
140
  describe("Rendering", () => {
78
141
  it("renders with all props", () => {
79
142
  render(<Footer {...defaultProps} />);
@@ -13,6 +13,7 @@ export const Footer: React.FC<FooterProps> = ({
13
13
  terms,
14
14
  maxWidth = true,
15
15
  onClick,
16
+ fieldProps,
16
17
  }) => {
17
18
  function footerClick(
18
19
  event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
@@ -23,7 +24,11 @@ export const Footer: React.FC<FooterProps> = ({
23
24
  return (
24
25
  <div className="component-container bg-bg-inverse text-white">
25
26
  <div className={` ${maxWidth ? "mx-auto max-w-120" : ""} px-5 py-8`}>
26
- <aside className="break-words" aria-label="Promotions and disclaimers">
27
+ <aside
28
+ className="break-words"
29
+ aria-label="Promotions and disclaimers"
30
+ {...fieldProps?.body}
31
+ >
27
32
  {body}
28
33
  </aside>
29
34
  <Divider className="my-8 border-border-inverse lg:my-16" />
@@ -36,7 +41,7 @@ export const Footer: React.FC<FooterProps> = ({
36
41
  className="site-links-group"
37
42
  key={`link-group-${index}`}
38
43
  >
39
- <Text as="div" className="label3">
44
+ <Text as="div" className="label3" {...link?.fieldProps?.title}>
40
45
  {link?.title}
41
46
  </Text>
42
47
  <ul className="flex flex-col">
@@ -61,10 +66,15 @@ export const Footer: React.FC<FooterProps> = ({
61
66
  <div className="py-8">
62
67
  <Divider className="my-8 hidden border-border-inverse md:my-8 md:block" />
63
68
 
64
- <p className="terms-text footnote text-white">
69
+ <p className="terms-text footnote text-white" {...fieldProps?.terms}>
65
70
  &copy; {new Date().getFullYear()} {terms}
66
71
  </p>
67
- <div className="copy-rights whitespace-pre-line">{copyrights}</div>
72
+ <div
73
+ className="copy-rights whitespace-pre-line"
74
+ {...fieldProps?.copyrights}
75
+ >
76
+ {copyrights}
77
+ </div>
68
78
  <nav aria-label="Footer legal">
69
79
  <ul className="bottom-links list-none">
70
80
  {bottomLinks?.map((link: any, index: number) => (
@@ -1,5 +1,7 @@
1
1
  import React from "react";
2
2
 
3
+ import type { FieldProps } from "@shared/types/field-props";
4
+
3
5
  export type FooterProps = {
4
6
  body: React.ReactNode;
5
7
  links?: Array<any>;
@@ -10,4 +12,6 @@ export type FooterProps = {
10
12
  onClick?: (
11
13
  event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
12
14
  ) => void;
15
+ /** Extra DOM attribute props, spread onto the respective rendered elements. */
16
+ fieldProps?: FieldProps<"body" | "terms" | "copyrights">;
13
17
  };
@@ -12,8 +12,15 @@ jest.mock("@shared/components/material-icon", () => ({
12
12
  }));
13
13
 
14
14
  jest.mock("@shared/components/text", () => ({
15
- Text: ({ as: Tag = "span", children, className }: any) => (
16
- <Tag className={className}>{children}</Tag>
15
+ Text: ({ as: Tag = "span", children, className, ...rest }: any) => (
16
+ <Tag
17
+ className={className}
18
+ {...Object.fromEntries(
19
+ Object.entries(rest).filter(([k]) => k.startsWith("data-"))
20
+ )}
21
+ >
22
+ {children}
23
+ </Tag>
17
24
  ),
18
25
  }));
19
26
 
@@ -50,6 +57,23 @@ jest.mock("@shared/utils", () => ({
50
57
  }));
51
58
 
52
59
  describe("DesktopLinkGroups", () => {
60
+ it("spreads the group's title fieldProps onto the title element", () => {
61
+ render(
62
+ <DesktopLinkGroups
63
+ anchorName="test"
64
+ link={{
65
+ title: "Shop",
66
+ items: { items: [] },
67
+ fieldProps: { title: { "data-contentful-field-id": "title" } },
68
+ }}
69
+ />
70
+ );
71
+ expect(screen.getByText("Shop")).toHaveAttribute(
72
+ "data-contentful-field-id",
73
+ "title"
74
+ );
75
+ });
76
+
53
77
  it("renders nothing when link is undefined", () => {
54
78
  const { container } = render(
55
79
  <DesktopLinkGroups anchorName="test" link={undefined} />
@@ -5,12 +5,14 @@ import { Text } from "@shared/components/text";
5
5
  import { Button } from "@shared/contentful/blocks/button";
6
6
  import { ButtonProps as ContentfulButtonProps } from "@shared/contentful/blocks/button/types";
7
7
  import { useOutsideClick } from "@shared/hooks/use-outside-click";
8
+ import type { FieldProps } from "@shared/types/field-props";
8
9
  import { cx } from "@shared/utils";
9
10
 
10
11
  type ComponentButtonGroup = {
11
12
  anchorId?: string | null;
12
13
  title?: string | null;
13
14
  items?: { items?: ContentfulButtonProps[] | null } | null;
15
+ fieldProps?: FieldProps<"title">;
14
16
  };
15
17
 
16
18
  type Link = ContentfulButtonProps | ComponentButtonGroup;
@@ -61,7 +63,7 @@ export const DesktopLinkGroups: React.FC<LinkGroupsProps> = ({
61
63
  }
62
64
 
63
65
  // Group
64
- const { anchorId, title, items } = link;
66
+ const { anchorId, title, items, fieldProps } = link;
65
67
  const subMenu = Array.isArray(items?.items) ? items!.items! : [];
66
68
  const fullAnchorName = `--link-anchor-${anchorName}`;
67
69
 
@@ -81,7 +83,11 @@ export const DesktopLinkGroups: React.FC<LinkGroupsProps> = ({
81
83
  key={anchorId}
82
84
  showButtonAs="unstyled"
83
85
  >
84
- <Text as="span" className="group-hover:underline">
86
+ <Text
87
+ as="span"
88
+ className="group-hover:underline"
89
+ {...fieldProps?.title}
90
+ >
85
91
  {title ?? null}
86
92
  </Text>
87
93
  <MaterialIcon
@@ -84,9 +84,16 @@ jest.mock("@shared/components/material-icon", () => ({
84
84
  }));
85
85
 
86
86
  jest.mock("@shared/components/next-image", () => ({
87
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
88
87
  NextImage: ({ src, alt, onClick, ...rest }: any) => (
89
- <img data-testid="next-image" src={src} alt={alt} onClick={onClick} />
88
+ <img
89
+ data-testid="next-image"
90
+ src={src}
91
+ alt={alt}
92
+ onClick={onClick}
93
+ {...Object.fromEntries(
94
+ Object.entries(rest).filter(([k]) => k.startsWith("data-"))
95
+ )}
96
+ />
90
97
  ),
91
98
  }));
92
99
 
@@ -149,6 +156,37 @@ const mobileMenuProps: NavigationProps = {
149
156
  describe("Navigation", () => {
150
157
  beforeEach(() => jest.clearAllMocks());
151
158
 
159
+ describe("fieldProps", () => {
160
+ const tag = (field: string) => ({ "data-contentful-field-id": field });
161
+
162
+ it("spreads logo fieldProps onto the desktop and mobile logo links", () => {
163
+ const { container } = render(
164
+ <Navigation {...defaultProps} fieldProps={{ logo: tag("logo") }} />
165
+ );
166
+ const tagged = container.querySelectorAll(
167
+ 'a[data-contentful-field-id="logo"]'
168
+ );
169
+ expect(tagged).toHaveLength(2);
170
+ });
171
+
172
+ it("spreads searchBarIcon fieldProps onto the desktop search icon", () => {
173
+ const { container } = render(
174
+ <Navigation
175
+ {...defaultProps}
176
+ fieldProps={{ searchBarIcon: tag("searchBarIcon") }}
177
+ />
178
+ );
179
+ expect(
180
+ container.querySelector('img[data-contentful-field-id="searchBarIcon"]')
181
+ ).not.toBeNull();
182
+ });
183
+
184
+ it("renders no tagging attributes when fieldProps is omitted", () => {
185
+ const { container } = render(<Navigation {...defaultProps} />);
186
+ expect(container.querySelector("[data-contentful-field-id]")).toBeNull();
187
+ });
188
+ });
189
+
152
190
  describe("enableHamburgerNavigationOnDesktop (desktop hamburger A/B variant)", () => {
153
191
  it("keeps current behavior when the flag is off", () => {
154
192
  const { container } = render(<Navigation {...defaultProps} />);
@@ -46,6 +46,7 @@ export const Navigation: React.FC<NavigationProps> = props => {
46
46
  callNowCtaIcon,
47
47
  navigationBackgroundColor,
48
48
  navigationLinkColor,
49
+ fieldProps,
49
50
  } = props;
50
51
 
51
52
  const logoUrl =
@@ -94,6 +95,7 @@ export const Navigation: React.FC<NavigationProps> = props => {
94
95
  width={24}
95
96
  height={24}
96
97
  className="mr-2 self-center rounded-full"
98
+ {...fieldProps?.callNowCtaIcon}
97
99
  />
98
100
  ) : null}
99
101
  <Text as="span" className="text-secondary-navy500 footnote">
@@ -159,7 +161,18 @@ export const Navigation: React.FC<NavigationProps> = props => {
159
161
  <CallButton
160
162
  className="border-none"
161
163
  href={invocaPhoneNumberLink}
162
- prefix={callNowCtaText}
164
+ prefix={
165
+ callNowCtaText ? (
166
+ <span
167
+ className="text-sm font-normal text-text"
168
+ {...fieldProps?.callNowCtaText}
169
+ >
170
+ {callNowCtaText}
171
+ </span>
172
+ ) : (
173
+ callNowCtaText
174
+ )
175
+ }
163
176
  onClick={onCallClickDesktop}
164
177
  >
165
178
  <Text className="body3 text-text">
@@ -210,7 +223,7 @@ export const Navigation: React.FC<NavigationProps> = props => {
210
223
  >
211
224
  <div>
212
225
  {logoUrl ? (
213
- <Link href="/" className="flex">
226
+ <Link href="/" className="flex" {...fieldProps?.logo}>
214
227
  <NextImage
215
228
  src={logoUrl}
216
229
  alt="Kinetic business logo"
@@ -265,7 +278,7 @@ export const Navigation: React.FC<NavigationProps> = props => {
265
278
  <div className="mx-auto flex h-14 max-w-120 items-center justify-between">
266
279
  <div className="flex h-full">
267
280
  {logoUrl ? (
268
- <Link href="/" className="flex">
281
+ <Link href="/" className="flex" {...fieldProps?.logo}>
269
282
  <NextImage
270
283
  src={logoUrl}
271
284
  alt="Kinetic business logo"
@@ -302,6 +315,7 @@ export const Navigation: React.FC<NavigationProps> = props => {
302
315
  : searchBarIcon?.url || ""
303
316
  }
304
317
  onSearch={onSearch}
318
+ iconFieldProps={fieldProps?.searchBarIcon}
305
319
  />
306
320
  ) : null}
307
321
  {supportNavigationLinks?.map((links, index) => {
@@ -448,6 +462,7 @@ const MobileMenu = (props: NavigationProps) => {
448
462
  : props.searchBarIcon?.url || ""
449
463
  }
450
464
  onSearch={props.onSearch || (() => {})}
465
+ iconFieldProps={props.fieldProps?.searchBarIcon}
451
466
  />
452
467
  ) : null}
453
468
  <div
@@ -510,8 +525,10 @@ const MobileSearchInput = (props: {
510
525
  onSearch: (query: string) => void;
511
526
  isMenuOpen: boolean;
512
527
  searchBarIconURL: string;
528
+ iconFieldProps?: Record<string, unknown>;
513
529
  }) => {
514
- const { closeMenu, onSearch, isMenuOpen, searchBarIconURL } = props;
530
+ const { closeMenu, onSearch, isMenuOpen, searchBarIconURL, iconFieldProps } =
531
+ props;
515
532
  const [searchValue, setSearchValue] = React.useState("");
516
533
  const searchInputRef = React.useRef<HTMLInputElement>(null);
517
534
 
@@ -541,6 +558,7 @@ const MobileSearchInput = (props: {
541
558
  alt="Search icon"
542
559
  role="button"
543
560
  onClick={redirectToSearchResults}
561
+ {...iconFieldProps}
544
562
  />
545
563
  <div className="flex-grow">
546
564
  <Input
@@ -561,8 +579,9 @@ const MobileSearchInput = (props: {
561
579
  const DesktopSearchInput = (props: {
562
580
  searchBarIconURL: string;
563
581
  onSearch: (query: string) => void;
582
+ iconFieldProps?: Record<string, unknown>;
564
583
  }) => {
565
- const { searchBarIconURL, onSearch } = props;
584
+ const { searchBarIconURL, onSearch, iconFieldProps } = props;
566
585
  const [searchValue, setSearchValue] = React.useState("");
567
586
  const searchInputRef = React.useRef<HTMLInputElement>(null);
568
587
 
@@ -585,6 +604,7 @@ const DesktopSearchInput = (props: {
585
604
  alt="Search icon"
586
605
  role="button"
587
606
  onClick={redirectToSearchResults}
607
+ {...iconFieldProps}
588
608
  />
589
609
  {/* Input */}
590
610
  <Input
@@ -28,6 +28,7 @@ jest.mock("@shared/contentful/blocks/button", () => ({
28
28
  className={buttonClassName || linkClassName}
29
29
  onClick={onClick}
30
30
  aria-expanded={rest["aria-expanded"]}
31
+ data-field-props={JSON.stringify(rest.fieldProps ?? null)}
31
32
  >
32
33
  {children || rest.buttonLabel}
33
34
  </button>
@@ -35,6 +36,19 @@ jest.mock("@shared/contentful/blocks/button", () => ({
35
36
  }));
36
37
 
37
38
  describe("MobileLinkGroups", () => {
39
+ it("passes the group's title fieldProps to the toggle button", () => {
40
+ const link = {
41
+ title: "Shop",
42
+ items: { items: [] },
43
+ fieldProps: { title: { "data-contentful-field-id": "title" } },
44
+ };
45
+ render(<MobileLinkGroups link={link as any} />);
46
+ expect(screen.getByTestId("button")).toHaveAttribute(
47
+ "data-field-props",
48
+ JSON.stringify({ "data-contentful-field-id": "title" })
49
+ );
50
+ });
51
+
38
52
  it("renders nothing when link is undefined", () => {
39
53
  const { container } = render(<MobileLinkGroups link={undefined} />);
40
54
  expect(container.innerHTML).toBe("");