@windstream/react-shared-components 0.2.17 → 0.2.19

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,23 +1,287 @@
1
1
  import { PrimaryHero } from "./index";
2
+ import type { PrimaryHeroProps } from "./types";
2
3
 
3
4
  import { DocsPage } from "@shared/stories/DocsTemplate";
4
- import type { Meta, StoryObj } from "@storybook/react";
5
+ import type { ArgTypes, Meta, StoryObj } from "@storybook/react";
6
+
7
+ // ─── Mock Data ────────────────────────────────────────────────────────────────
8
+
9
+ const HERO_IMAGE =
10
+ "https://images.ctfassets.net/8d4yn2ywtegc/6Dpv8KVrKnuQXy0CCSHFa8/8cf420b6d55f6bdcfa443f7cff75e294/1a9b1046b0b507d52c57172edc6324f1417ff402.png";
11
+
12
+ const BADGE_IMAGE =
13
+ "https://images.ctfassets.net/8d4yn2ywtegc/7hlBQ8QOkl0fI8vawenlJK/216b89b30eb9ec3685f723696baeefdf/promo_badge__1_.svg";
14
+
15
+ const DEFAULT_CHECKLIST: PrimaryHeroProps["checklist"] = [
16
+ { title: "100% network reliability" },
17
+ { title: "Free Whole Home Wi-Fi Set Up" },
18
+ { title: "Free Kinetic Secure for 3 months" },
19
+ { title: "No annual contract or data caps" },
20
+ ];
21
+
22
+ const CHECK_AVAILABILITY_CTA = {
23
+ showButtonAs: "solid" as const,
24
+ buttonVariant: "primary_brand" as const,
25
+ buttonLabel: "Check availability",
26
+ href: "#",
27
+ target: "_self" as const,
28
+ };
29
+
30
+ // ─── ArgTypes ─────────────────────────────────────────────────────────────────
31
+
32
+ const argTypes: ArgTypes<PrimaryHeroProps> = {
33
+ title: {
34
+ control: { type: "text" as const },
35
+ description: "Main headline text displayed in the hero",
36
+ },
37
+ showAsHeading: {
38
+ control: { type: "boolean" as const },
39
+ description: "Renders title as h1 instead of h2",
40
+ },
41
+ subTitle: {
42
+ control: { type: "text" as const },
43
+ description:
44
+ "Subtitle text below the headline highlighting key value proposition",
45
+ },
46
+ price: {
47
+ control: { type: "text" as const },
48
+ description: "Price value (e.g. '24')",
49
+ },
50
+ priceSuffix: {
51
+ control: { type: "text" as const },
52
+ description: "Price suffix displayed after the price (e.g. '/mo')",
53
+ },
54
+ priceCallout: {
55
+ control: { type: "text" as const },
56
+ description:
57
+ "Price callout text, pipe-separated for multiple lines (e.g. 'Fiber plans|starting at')",
58
+ },
59
+ pricingDescription: {
60
+ control: { type: "text" as const },
61
+ description: "Descriptive text displayed below the pricing section",
62
+ },
63
+ pricingDescriptionDisclaimer: {
64
+ control: { type: "text" as const },
65
+ description: "Smaller disclaimer text below the pricing description",
66
+ },
67
+ checklist: {
68
+ control: { type: "object" as const },
69
+ description: "Array of checklist items with title and optional icon URL",
70
+ },
71
+ logoLockup: {
72
+ control: { type: "text" as const },
73
+ description: "URL for logo lockup image displayed below checklist",
74
+ },
75
+ carouselImages: {
76
+ control: { type: "object" as const },
77
+ description: "Array of hero image URLs",
78
+ },
79
+ squareImage: {
80
+ control: false,
81
+ table: { disable: true },
82
+ },
83
+ badgeImage: {
84
+ control: { type: "text" as const },
85
+ description: "URL for promo badge image overlaid on the hero image",
86
+ },
87
+ textColor: {
88
+ control: { type: "select" as const },
89
+ options: ["light", "dark"],
90
+ description:
91
+ "Text color theme — light (white) for dark backgrounds, dark for light backgrounds",
92
+ },
93
+ primaryCta1: {
94
+ control: { type: "object" as const },
95
+ description: "Primary call-to-action button props",
96
+ },
97
+ secondaryCtaPrefix: {
98
+ control: { type: "text" as const },
99
+ description: "Prefix text before the secondary CTA (e.g. 'Or')",
100
+ },
101
+ secondaryCta: {
102
+ control: { type: "object" as const },
103
+ description: "Secondary call-to-action link/button props",
104
+ },
105
+ bottomLink: {
106
+ control: { type: "object" as const },
107
+ description: "Bottom link rendered below the CTA section",
108
+ },
109
+ hideOnMobileCta1: {
110
+ control: { type: "boolean" as const },
111
+ description: "Hides the primary CTA on mobile viewports",
112
+ },
113
+ maxWidth: {
114
+ table: { disable: true },
115
+ },
116
+ checkAvailabilityEyebrow: {
117
+ control: { disable: true },
118
+ table: { disable: true },
119
+ },
120
+ renderCheckPlans: {
121
+ control: { disable: true },
122
+ table: { disable: true },
123
+ },
124
+ onModalButtonClick: {
125
+ control: { disable: true },
126
+ table: { disable: true },
127
+ },
128
+ };
129
+
130
+ // ─── Meta ─────────────────────────────────────────────────────────────────────
5
131
 
6
132
  const meta: Meta<typeof PrimaryHero> = {
7
133
  title: "Contentful Blocks/PrimaryHero",
8
134
  component: PrimaryHero,
9
135
  tags: ["autodocs"],
136
+ argTypes,
10
137
  parameters: {
11
- layout: "centered",
138
+ layout: "fullscreen",
12
139
  docs: {
13
140
  page: DocsPage,
14
141
  description: {
15
- component: "Contentful primary hero block.",
142
+ component:
143
+ "Contentful primary hero block featuring headline, subtitle, pricing, checklist, CTA buttons, logo lockup, and responsive hero image with optional promo badge.",
16
144
  },
17
145
  },
18
146
  },
19
- args: {},
147
+ decorators: [
148
+ Story => (
149
+ <div style={{ minWidth: 1280 }}>
150
+ <Story />
151
+ </div>
152
+ ),
153
+ ],
154
+ args: {
155
+ title: "Internet better with ultra-fast fiber internet.",
156
+ showAsHeading: true,
157
+ price: "24",
158
+ priceSuffix: "/mo",
159
+ priceCallout: "Fiber plans|starting at",
160
+ checklist: DEFAULT_CHECKLIST,
161
+ logoLockup: undefined,
162
+ carouselImages: [HERO_IMAGE],
163
+ badgeImage: BADGE_IMAGE,
164
+ primaryCta1: CHECK_AVAILABILITY_CTA,
165
+ textColor: "dark",
166
+ },
20
167
  };
168
+
21
169
  export default meta;
22
170
  type Story = StoryObj<typeof meta>;
171
+
172
+ // ─── Stories ──────────────────────────────────────────────────────────────────
173
+
174
+ /** Default hero with all main features: headline, pricing, checklist, logo lockup, badge, and hero image. */
23
175
  export const Default: Story = {};
176
+
177
+ /** Promo badge overlaid on the hero image — draws attention to a limited-time offer. */
178
+ export const PromoBadge: Story = {
179
+ parameters: {
180
+ docs: {
181
+ description: {
182
+ story:
183
+ "Promo badge (e.g. $3.99 or limited-time price) is rendered as an overlay on the top-left corner of the hero image.",
184
+ },
185
+ },
186
+ },
187
+ args: {
188
+ title: "Internet better with ultra-fast fiber internet.",
189
+ badgeImage: BADGE_IMAGE,
190
+ checklist: DEFAULT_CHECKLIST,
191
+ },
192
+ };
193
+
194
+ /** Hero with only a headline — no subtitle or pricing. */
195
+ export const Headline: Story = {
196
+ parameters: {
197
+ docs: {
198
+ description: {
199
+ story:
200
+ "Displays only the headline text with checklist and CTA. Use when the hero needs minimal content.",
201
+ },
202
+ },
203
+ },
204
+ args: {
205
+ title: "Hero headline.",
206
+ subTitle: undefined,
207
+ price: undefined,
208
+ priceSuffix: undefined,
209
+ priceCallout: undefined,
210
+ pricingDescription: undefined,
211
+ logoLockup: undefined,
212
+ badgeImage: undefined,
213
+ checklist: DEFAULT_CHECKLIST,
214
+ },
215
+ };
216
+
217
+ /** Hero with subtitle text below the headline highlighting a key value proposition. */
218
+ export const SubTitle: Story = {
219
+ parameters: {
220
+ docs: {
221
+ description: {
222
+ story:
223
+ "Uses the subtitle property to display supporting text beneath the headline, reinforcing the value proposition.",
224
+ },
225
+ },
226
+ },
227
+ args: {
228
+ title: "Internet better with ultra-fast fiber internet.",
229
+ subTitle: "Subtitle highlighting key value proposition.",
230
+ price: undefined,
231
+ priceSuffix: undefined,
232
+ priceCallout: undefined,
233
+ pricingDescription: undefined,
234
+ logoLockup: undefined,
235
+ badgeImage: undefined,
236
+ checklist: DEFAULT_CHECKLIST,
237
+ },
238
+ };
239
+
240
+ /** Hero with pricing callout section including price, suffix, description and disclaimer. */
241
+ export const PricingCallout: Story = {
242
+ parameters: {
243
+ docs: {
244
+ description: {
245
+ story:
246
+ "Displays the full pricing section with callout prefix, price value, suffix, and optional description/disclaimer text.",
247
+ },
248
+ },
249
+ },
250
+ args: {
251
+ title: "Internet better with ultra-fast fiber internet.",
252
+ subTitle:
253
+ "Experience the joy of ultra-fast, reliable fiber internet in every room of your home.",
254
+ price: "24",
255
+ priceSuffix: "/mo",
256
+ priceCallout: "starting at",
257
+ pricingDescription: "pricing disclaimer text goes here",
258
+ logoLockup: undefined,
259
+ badgeImage: undefined,
260
+ checklist: undefined,
261
+ },
262
+ };
263
+
264
+ /** Bundle offer variant with different messaging and pricing. */
265
+ export const BundleOffer: Story = {
266
+ parameters: {
267
+ docs: {
268
+ description: {
269
+ story:
270
+ "Demonstrates the hero used for bundle/cross-sell offers with custom headline and pricing copy.",
271
+ },
272
+ },
273
+ },
274
+ args: {
275
+ title: "Bundle and save on Kinetic Fiber Internet.",
276
+ subTitle:
277
+ "Save $20/mo off your Kinetic bill with a new or existing AT&T Wireless plan*.",
278
+ price: undefined,
279
+ priceSuffix: undefined,
280
+ priceCallout: undefined,
281
+ pricingDescription: undefined,
282
+ pricingDescriptionDisclaimer: "Pricing disclaimer text goes here.",
283
+ logoLockup: undefined,
284
+ badgeImage: undefined,
285
+ checklist: undefined,
286
+ },
287
+ };
@@ -229,7 +229,7 @@ describe("PrimaryHero", () => {
229
229
  pricingDescriptionDisclaimer="Taxes apply"
230
230
  />
231
231
  );
232
- expect(screen.getAllByText("Taxes apply")).toHaveLength(2);
232
+ expect(screen.getAllByText("Taxes apply")).toHaveLength(1);
233
233
  });
234
234
 
235
235
  it("does not render disclaimer when not provided", () => {
@@ -21,14 +21,18 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
21
21
  checklist,
22
22
  renderCheckPlans,
23
23
  badgeImage,
24
+ pricingDescription,
24
25
  pricingDescriptionDisclaimer,
25
26
  secondaryCta,
26
27
  secondaryCtaPrefix,
27
28
  onModalButtonClick,
28
29
  hideOnMobileCta1,
30
+ textColor = "light",
31
+ logoLockup,
29
32
  } = props;
30
33
 
31
34
  const bottomLinkLabel = bottomLink?.buttonLabel ?? bottomLink?.label;
35
+ const textColorClass = textColor === "light" ? " text-white" : " text-text";
32
36
  return (
33
37
  <div className="component-container p-5 xl:flex xl:min-h-[724px] xl:items-center xl:p-0 xl:py-4">
34
38
  <div
@@ -37,7 +41,11 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
37
41
  )}
38
42
  >
39
43
  {/* Left column: navy background, headline, body, address, button, link */}
40
- <div className={cx("relative flex flex-col text-white sm:w-[485px]")}>
44
+ <div
45
+ className={cx(
46
+ `relative flex flex-col ${textColorClass} sm:w-[485px]`
47
+ )}
48
+ >
41
49
  <div className="flex flex-col gap-5 lg:gap-6">
42
50
  {title && (
43
51
  <Text
@@ -81,10 +89,25 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
81
89
  </div>
82
90
  ) : null}
83
91
 
92
+ {pricingDescription ? (
93
+ <Text
94
+ data-cy="hero-pricing-disclaimer"
95
+ className={cx("text-left sm:text-center xl:text-left")}
96
+ >
97
+ {pricingDescription}
98
+ </Text>
99
+ ) : null}
100
+
101
+ {pricingDescriptionDisclaimer ? (
102
+ <Text data-cy="hero-pricing-disclaimer" as="p" className="body3">
103
+ {pricingDescriptionDisclaimer}
104
+ </Text>
105
+ ) : null}
106
+
84
107
  {/* checklist */}
85
108
  {checklist && (
86
109
  <Checklist
87
- listItemClassName="body2 text-white text-left"
110
+ listItemClassName={`body2 text-left ${textColorClass}`}
88
111
  items={checklist.map(item =>
89
112
  item.iconUrl
90
113
  ? { title: item.title, iconUrl: item.iconUrl }
@@ -93,6 +116,20 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
93
116
  />
94
117
  )}
95
118
 
119
+ {logoLockup ? (
120
+ <div className="text-left sm:text-center xl:mb-0 xl:text-left">
121
+ <NextImage
122
+ src={logoLockup}
123
+ alt={"Logo Lockup"}
124
+ className="inline-block"
125
+ width={201}
126
+ height={24}
127
+ quality={90}
128
+ loading="eager"
129
+ />
130
+ </div>
131
+ ) : null}
132
+
96
133
  {/* CTA section */}
97
134
  <div className="flex flex-col gap-3">
98
135
  {/* desktop CTA */}
@@ -107,7 +144,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
107
144
  />
108
145
  </div>
109
146
  )}
110
- {secondaryCtaPrefix || secondaryCta ? (
147
+ {secondaryCtaPrefix && secondaryCta ? (
111
148
  <div className="hidden md:flex">
112
149
  {secondaryCtaPrefix && (
113
150
  <Text as="span" className="body2 mr-1">
@@ -116,7 +153,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
116
153
  )}
117
154
  {secondaryCta && (
118
155
  <Button
119
- linkClassName="body2 text-text-inverse"
156
+ linkClassName={`body2 ${textColorClass}`}
120
157
  onModalButtonClick={onModalButtonClick}
121
158
  {...secondaryCta}
122
159
  />
@@ -129,17 +166,11 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
129
166
  <div className="hidden md:block">
130
167
  <Button
131
168
  {...bottomLink}
132
- linkClassName="text-footnote text-white"
169
+ linkClassName={`text-footnote ${textColorClass}`}
133
170
  onModalButtonClick={onModalButtonClick}
134
171
  />
135
172
  </div>
136
173
  )}
137
-
138
- {pricingDescriptionDisclaimer ? (
139
- <Text as="p" className="body3 hidden md:block">
140
- {pricingDescriptionDisclaimer}
141
- </Text>
142
- ) : null}
143
174
  </div>
144
175
  </div>
145
176
  </div>
@@ -177,19 +208,15 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
177
208
  <Button
178
209
  {...bottomLink}
179
210
  onModalButtonClick={onModalButtonClick}
180
- linkClassName="body3 text-white"
211
+ linkClassName={`body3 ${textColorClass}`}
181
212
  />
182
213
  </div>
183
214
  )}
184
215
 
185
- {pricingDescriptionDisclaimer ? (
186
- <Text as="p" className="body3 md:hidden">
187
- {pricingDescriptionDisclaimer}
188
- </Text>
189
- ) : null}
190
-
191
216
  {secondaryCtaPrefix || secondaryCta ? (
192
- <div className="flex flex-wrap items-center text-white md:hidden">
217
+ <div
218
+ className={`flex flex-wrap items-center ${textColorClass} md:hidden`}
219
+ >
193
220
  {secondaryCtaPrefix && (
194
221
  <Text as="span" className="body2 mr-1">
195
222
  {secondaryCtaPrefix}
@@ -199,7 +226,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
199
226
  <Button
200
227
  {...secondaryCta}
201
228
  onModalButtonClick={onModalButtonClick}
202
- linkClassName="text-white"
229
+ linkClassName={`${textColorClass}`}
203
230
  />
204
231
  )}
205
232
  </div>
@@ -30,7 +30,7 @@ export type PrimaryHeroProps = {
30
30
  carouselImages?: string[];
31
31
  squareImage?: boolean;
32
32
  badgeImage?: string;
33
- textColor?: string;
33
+ textColor?: "light" | "dark";
34
34
  maxWidth?: boolean;
35
35
  renderCheckPlans?: (overrides?: CheckPlansProps) => React.ReactNode;
36
36
  onModalButtonClick?: (id?: string) => void;