@windstream/react-shared-components 0.2.19 → 0.2.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windstream/react-shared-components",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "type": "module",
5
5
  "description": "Shared React components for Kinetic applications",
6
6
  "main": "dist/index.js",
@@ -109,6 +109,7 @@ export function AnimationWrapper({
109
109
  return (
110
110
  <MotionComponent
111
111
  {...child.props}
112
+ ref={(child as any).ref}
112
113
  whileHover={mergedWhileHover}
113
114
  whileTap={mergedWhileTap}
114
115
  transition={mergedTransition}
@@ -3,6 +3,7 @@
3
3
  import React, { forwardRef } from "react";
4
4
  import { getLabelSizeBasedOnButtonSize, sizeToClassNames } from "./helpers";
5
5
 
6
+ import { AnimationWrapper } from "@shared/components/animation-wrapper";
6
7
  import {
7
8
  ButtonProps,
8
9
  ButtonVariantsT,
@@ -29,6 +30,8 @@ export const BrandButton = forwardRef<
29
30
  iconSize = 24,
30
31
  iconFill = 0,
31
32
  as = "button",
33
+ disableAnimation = true,
34
+ animationType,
32
35
  ...props
33
36
  },
34
37
  ref
@@ -99,29 +102,39 @@ export const BrandButton = forwardRef<
99
102
 
100
103
  if (as === "a") {
101
104
  return (
102
- <a
103
- ref={ref as React.Ref<HTMLAnchorElement>}
104
- className={cx(combinedClassName)}
105
- {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}
106
- data-component="BrandButton"
107
- data-variant={"anchor"}
105
+ <AnimationWrapper
106
+ disableAnimation={disableAnimation}
107
+ animationType={animationType}
108
108
  >
109
- {contentEl}
110
- </a>
109
+ <a
110
+ ref={ref as React.Ref<HTMLAnchorElement>}
111
+ className={cx(combinedClassName)}
112
+ {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}
113
+ data-component="BrandButton"
114
+ data-variant={"anchor"}
115
+ >
116
+ {contentEl}
117
+ </a>
118
+ </AnimationWrapper>
111
119
  );
112
120
  }
113
121
 
114
122
  return (
115
- <button
116
- ref={ref as React.Ref<HTMLButtonElement>}
117
- className={cx(combinedClassName)}
118
- disabled={disabled || isLoading}
119
- {...(props as React.ButtonHTMLAttributes<HTMLButtonElement>)}
120
- data-component="BrandButton"
121
- data-variant={"button"}
123
+ <AnimationWrapper
124
+ disableAnimation={disableAnimation}
125
+ animationType={animationType}
122
126
  >
123
- {contentEl}
124
- </button>
127
+ <button
128
+ ref={ref as React.Ref<HTMLButtonElement>}
129
+ className={cx(combinedClassName)}
130
+ disabled={disabled || isLoading}
131
+ {...(props as React.ButtonHTMLAttributes<HTMLButtonElement>)}
132
+ data-component="BrandButton"
133
+ data-variant={"button"}
134
+ >
135
+ {contentEl}
136
+ </button>
137
+ </AnimationWrapper>
125
138
  );
126
139
  }
127
140
  );
@@ -5,6 +5,7 @@ import type {
5
5
  IconName,
6
6
  Size,
7
7
  } from "@shared/components/material-icon/types";
8
+ import type { AnimationType } from "@shared/components/animation-wrapper";
8
9
 
9
10
  export type ButtonVariantsT = "primary_brand" | "primary_inverse" | "secondary";
10
11
 
@@ -30,6 +31,8 @@ export type ButtonCustomProps = {
30
31
  iconName?: IconName;
31
32
  iconSize?: Size;
32
33
  iconFill?: Fill;
34
+ disableAnimation?: boolean;
35
+ animationType?: AnimationType | AnimationType[];
33
36
  };
34
37
 
35
38
  // Discriminated union types for polymorphic component
@@ -121,6 +121,8 @@ export const Button: React.FC<ButtonProps> = props => {
121
121
  buttonClassName={buttonClassName}
122
122
  size={size}
123
123
  onClick={linkClick}
124
+ disableAnimation={disableAnimation}
125
+ animationType={animationType}
124
126
  />
125
127
  ) : (
126
128
  <BrandButton
@@ -132,6 +134,8 @@ export const Button: React.FC<ButtonProps> = props => {
132
134
  size={size}
133
135
  onClick={buttonClick}
134
136
  buttonClassName={buttonClassName}
137
+ disableAnimation={disableAnimation}
138
+ animationType={animationType}
135
139
  />
136
140
  );
137
141
  break;
@@ -172,6 +176,8 @@ export const Button: React.FC<ButtonProps> = props => {
172
176
  iconName={iconName as any}
173
177
  iconSize={iconSize}
174
178
  iconFill={iconFill}
179
+ disableAnimation={disableAnimation}
180
+ animationType={animationType}
175
181
  />
176
182
  ) : (
177
183
  <BrandButton
@@ -186,6 +192,8 @@ export const Button: React.FC<ButtonProps> = props => {
186
192
  iconName={iconName as any}
187
193
  iconSize={iconSize}
188
194
  iconFill={iconFill}
195
+ disableAnimation={disableAnimation}
196
+ animationType={animationType}
189
197
  />
190
198
  );
191
199
  break;
@@ -0,0 +1,168 @@
1
+ import CartRetentionBanner from "./index";
2
+
3
+ import { DocsPage } from "@shared/stories/DocsTemplate";
4
+ import type { Meta, StoryObj } from "@storybook/react";
5
+
6
+ const meta: Meta<typeof CartRetentionBanner> = {
7
+ title: "Contentful Blocks/CartRetentionBanner",
8
+ component: CartRetentionBanner,
9
+ tags: ["autodocs"],
10
+ parameters: {
11
+ layout: "centered",
12
+ docs: {
13
+ page: DocsPage,
14
+ description: {
15
+ component:
16
+ "A cart retention banner that displays a modal or inline popup to encourage users to complete their checkout.",
17
+ },
18
+ },
19
+ },
20
+ argTypes: {
21
+ fields: {
22
+ control: { type: "object" },
23
+ description:
24
+ "Content fields including anchorId, mainHeading, description (rich-text), and cta.buttonLabel",
25
+ },
26
+ isInPopupContainer: {
27
+ control: { type: "boolean" },
28
+ description: "Renders inline popup content without the modal wrapper",
29
+ },
30
+ onClose: {
31
+ action: "onClose",
32
+ description: "Callback when the banner is closed",
33
+ },
34
+ onContinueCheckout: {
35
+ action: "onContinueCheckout",
36
+ description: "Callback when continue checkout is clicked",
37
+ },
38
+ checkShouldShowBanner: {
39
+ control: false,
40
+ description: "Function that determines whether the banner should show",
41
+ },
42
+ onBannerShown: {
43
+ action: "onBannerShown",
44
+ description: "Callback when the banner is displayed",
45
+ },
46
+ },
47
+ args: {
48
+ fields: {
49
+ entryTitle: "Cart Retention Banner",
50
+ anchorId: "cart-retention-banner",
51
+ mainHeading: "Welcome back. Let's finish your order",
52
+ description: {
53
+ json: {
54
+ nodeType: "document",
55
+ data: {},
56
+ content: [
57
+ {
58
+ nodeType: "paragraph",
59
+ data: {},
60
+ content: [
61
+ {
62
+ nodeType: "text",
63
+ value:
64
+ "You have items waiting in your cart. Complete your order now!",
65
+ marks: [],
66
+ data: {},
67
+ },
68
+ ],
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ cta: {
74
+ buttonLabel: "Continue checkout",
75
+ },
76
+ },
77
+ checkShouldShowBanner: () => true,
78
+ isInPopupContainer: false,
79
+ },
80
+ };
81
+
82
+ export default meta;
83
+ type Story = StoryObj<typeof meta>;
84
+
85
+ // Default modal variant (checkShouldShowBanner returns true to open modal)
86
+ export const Default: Story = {};
87
+
88
+ // Inline popup container variant
89
+ export const InlinePopup: Story = {
90
+ args: {
91
+ isInPopupContainer: true,
92
+ },
93
+ };
94
+
95
+ // Custom heading and CTA label
96
+ export const CustomHeadingAndCta: Story = {
97
+ args: {
98
+ isInPopupContainer: true,
99
+ fields: {
100
+ entryTitle: "Cart Retention Banner",
101
+ anchorId: "cart-retention-custom",
102
+ mainHeading: "Don't forget your items!",
103
+ description: {
104
+ json: {
105
+ nodeType: "document",
106
+ data: {},
107
+ content: [
108
+ {
109
+ nodeType: "paragraph",
110
+ data: {},
111
+ content: [
112
+ {
113
+ nodeType: "text",
114
+ value: "Your cart is waiting for you. Finish checkout today.",
115
+ marks: [],
116
+ data: {},
117
+ },
118
+ ],
119
+ },
120
+ ],
121
+ },
122
+ },
123
+ cta: {
124
+ buttonLabel: "Complete my order",
125
+ },
126
+ },
127
+ },
128
+ };
129
+ // With description
130
+ export const WithDescription: Story = {
131
+ args: {
132
+ isInPopupContainer: true,
133
+ fields: {
134
+ entryTitle: "Cart Retention Banner",
135
+ anchorId: "cart-retention-with-desc",
136
+ mainHeading: "Welcome back. Let's finish your order",
137
+ description: {
138
+ json: {
139
+ nodeType: "document",
140
+ data: {},
141
+ content: [
142
+ {
143
+ nodeType: "paragraph",
144
+ data: {},
145
+ content: [
146
+ {
147
+ nodeType: "text",
148
+ value: "You have items waiting in your cart. ",
149
+ marks: [],
150
+ data: {},
151
+ },
152
+ {
153
+ nodeType: "text",
154
+ value: "Complete your order now",
155
+ marks: [],
156
+ data: {},
157
+ },
158
+ ],
159
+ },
160
+ ],
161
+ },
162
+ },
163
+ cta: {
164
+ buttonLabel: "Continue checkout",
165
+ },
166
+ },
167
+ },
168
+ };
@@ -0,0 +1,182 @@
1
+ import { EmailInputBlock } from "./index";
2
+
3
+ import { DocsPage } from "@shared/stories/DocsTemplate";
4
+ import type { Meta, StoryObj } from "@storybook/react";
5
+
6
+ const meta: Meta<typeof EmailInputBlock> = {
7
+ title: "Contentful Blocks/EmailInputBlock",
8
+ component: EmailInputBlock,
9
+ tags: ["autodocs"],
10
+ parameters: {
11
+ layout: "fullscreen",
12
+ docs: {
13
+ page: DocsPage,
14
+ description: {
15
+ component:
16
+ "Contentful EmailInputBlock component. Renders a full-width email sign-up section with a title, description, email input field, and submit button. Supports multiple background color themes and displays validation errors and a success message after valid submission.",
17
+ },
18
+ },
19
+ },
20
+ argTypes: {
21
+ backgroundColor: {
22
+ control: { type: "select" },
23
+ options: ["green", "white", "yellow", "blue", "navy"],
24
+ description: "Background color theme of the block",
25
+ },
26
+ title: {
27
+ control: { type: "text" },
28
+ description: "Main heading displayed at the top of the block",
29
+ },
30
+ subTitle: {
31
+ control: { type: "text" },
32
+ description: "Secondary heading displayed below the title",
33
+ },
34
+ emailInputDescription: {
35
+ control: { type: "text" },
36
+ description: "Descriptive paragraph shown above the email input",
37
+ },
38
+ inputPlaceholder: {
39
+ control: { type: "text" },
40
+ description: "Placeholder text shown inside the email input field",
41
+ },
42
+ ctaText: {
43
+ control: { type: "text" },
44
+ description: "Label text for the submit button",
45
+ },
46
+ successFeedback: {
47
+ control: { type: "text" },
48
+ description:
49
+ "Message shown below the input after a successful form submission",
50
+ },
51
+ anchorId: {
52
+ control: { type: "text" },
53
+ description: "HTML id attribute on the section element, used for scroll-to-section links",
54
+ },
55
+ caption: {
56
+ control: { type: "text" },
57
+ description: "Optional disclaimer or caption text rendered below the colored block",
58
+ },
59
+ onSubmit: {
60
+ action: "submitted",
61
+ description: "Callback fired with the submitted email value when the form is valid",
62
+ },
63
+ },
64
+ args: {
65
+ title: "Stay in the know",
66
+ emailInputDescription: "Sign up for updates, tips, and exclusive offers from Kinetic.",
67
+ inputPlaceholder: "Enter your email here",
68
+ ctaText: "Sign me up",
69
+ backgroundColor: "green",
70
+ anchorId: "email-input",
71
+ },
72
+ };
73
+
74
+ export default meta;
75
+ type Story = StoryObj<typeof meta>;
76
+
77
+ // ─── Default ──────────────────────────────────────────────────────────────────
78
+
79
+ export const Default: Story = {};
80
+
81
+ // ─── Background Color Variants ────────────────────────────────────────────────
82
+
83
+ export const GreenBackground: Story = {
84
+ args: {
85
+ backgroundColor: "green",
86
+ },
87
+ parameters: {
88
+ docs: {
89
+ description: {
90
+ story: "EmailInputBlock with a green background. Text is white.",
91
+ },
92
+ },
93
+ },
94
+ };
95
+
96
+ export const WhiteBackground: Story = {
97
+ args: {
98
+ backgroundColor: "white",
99
+ },
100
+ parameters: {
101
+ docs: {
102
+ description: {
103
+ story: "EmailInputBlock with a white background. Text is dark.",
104
+ },
105
+ },
106
+ },
107
+ };
108
+
109
+ export const YellowBackground: Story = {
110
+ args: {
111
+ backgroundColor: "yellow",
112
+ },
113
+ parameters: {
114
+ docs: {
115
+ description: {
116
+ story: "EmailInputBlock with a yellow background. Text is dark.",
117
+ },
118
+ },
119
+ },
120
+ };
121
+
122
+ export const BlueBackground: Story = {
123
+ args: {
124
+ backgroundColor: "blue",
125
+ },
126
+ parameters: {
127
+ docs: {
128
+ description: {
129
+ story: "EmailInputBlock with a blue background. Text is white.",
130
+ },
131
+ },
132
+ },
133
+ };
134
+
135
+ export const NavyBackground: Story = {
136
+ args: {
137
+ backgroundColor: "navy",
138
+ },
139
+ parameters: {
140
+ docs: {
141
+ description: {
142
+ story: "EmailInputBlock with a navy background. Text is white.",
143
+ },
144
+ },
145
+ },
146
+ };
147
+
148
+ // ─── Success Feedback ─────────────────────────────────────────────────────────
149
+
150
+ export const WithSuccessFeedback: Story = {
151
+ args: {
152
+ successFeedback: "Thanks! You're signed up for updates from Kinetic.",
153
+ },
154
+ parameters: {
155
+ docs: {
156
+ description: {
157
+ story:
158
+ "Demonstrates the success message after a valid email is submitted. Enter a valid email address (e.g. user@example.com) and click 'Sign me up' to see the successFeedback message appear below the input.",
159
+ },
160
+ },
161
+ },
162
+ };
163
+
164
+ // ─── Validation States ────────────────────────────────────────────────────────
165
+
166
+ export const ValidationStates: Story = {
167
+ args: {
168
+ successFeedback: "You're all set! Welcome to Kinetic updates.",
169
+ },
170
+ parameters: {
171
+ docs: {
172
+ description: {
173
+ story:
174
+ "Use this story to interactively test validation states on the canvas:\n\n" +
175
+ "**Empty field error:** Click 'Sign me up' without entering anything → shows *'Email is required'*\n\n" +
176
+ "**Invalid email error:** Type a malformed email (e.g. `hello` or `hello@`) and click 'Sign me up' → shows *'Invalid email address'*\n\n" +
177
+ "**Success state:** Enter a valid email (e.g. `user@example.com`) and click 'Sign me up' → shows the success message.",
178
+ },
179
+ },
180
+ },
181
+ };
182
+
@@ -0,0 +1,11 @@
1
+ import type { FindKineticProps } from "./types";
2
+
3
+ export const LOCATION_LIST: NonNullable<FindKineticProps["list"]> = [
4
+ { name: "New York", code: "new-york" },
5
+ { name: "California", code: "california" },
6
+ { name: "Texas", code: "texas" },
7
+ { name: "Florida", code: "florida" },
8
+ { name: "Illinois", code: "illinois" },
9
+ { name: "Ohio", code: "ohio" },
10
+ { name: "Georgia", code: "georgia" },
11
+ ];
@@ -1,23 +1,140 @@
1
+ import { LOCATION_LIST } from "./FindKinetic.stories.mocks";
1
2
  import { FindKinetic } from "./index";
3
+ import type { FindKineticProps } from "./types";
2
4
 
3
5
  import { DocsPage } from "@shared/stories/DocsTemplate";
4
- import type { Meta, StoryObj } from "@storybook/react";
6
+ import type { ArgTypes, Meta, StoryObj } from "@storybook/react";
7
+
8
+ const argTypes: ArgTypes<FindKineticProps> = {
9
+ title: {
10
+ control: { type: "text" },
11
+ description: "Main heading of the block",
12
+ },
13
+ subTitle: {
14
+ control: { type: "text" },
15
+ description: "Sub-heading below the title",
16
+ },
17
+ description: {
18
+ control: { type: "text" },
19
+ description: "Body copy above the list",
20
+ },
21
+ enableHeading: {
22
+ control: { type: "boolean" },
23
+ description: "When true, renders title as h1 and subTitle as h2",
24
+ },
25
+ background: {
26
+ control: { type: "select" },
27
+ options: ["blue", "green", "navy", "purple", "white", "yellow"],
28
+ description: "Background color of the block",
29
+ },
30
+ color: {
31
+ control: { type: "radio" },
32
+ options: ["dark", "light"],
33
+ description: "Text color theme — use light on dark backgrounds",
34
+ },
35
+ image: {
36
+ control: { type: "text" },
37
+ description: "URL of the decorative aside image",
38
+ },
39
+ list: {
40
+ control: { type: "object" },
41
+ description: "Array of { name, code } location objects",
42
+ },
43
+ localPathPrefix: {
44
+ control: { type: "text" },
45
+ description: "Path prefix prepended to each location code link",
46
+ },
47
+ maxWidth: {
48
+ control: { type: "boolean" },
49
+ description: "Constrains inner content to a max-width container",
50
+ },
51
+ columns: {
52
+ control: { type: "select" },
53
+ options: [2, 3, 4],
54
+ description: "Number of columns in the list grid (not yet implemented in component)",
55
+ },
56
+ };
5
57
 
6
58
  const meta: Meta<typeof FindKinetic> = {
7
59
  title: "Contentful Blocks/FindKinetic",
8
60
  component: FindKinetic,
9
61
  tags: ["autodocs"],
62
+ argTypes,
10
63
  parameters: {
11
- layout: "centered",
64
+ layout: "fullscreen",
12
65
  docs: {
13
66
  page: DocsPage,
14
67
  description: {
15
- component: "Contentful FindKinetic block.",
68
+ component: "A location finder block that displays a themed, full-width section with a linked list of serviceable locations. Supports 6 background colour themes (blue, green, navy, purple, white, yellow), light/dark text modes, an optional decorative aside image, and a responsive multi-column location link grid.",
16
69
  },
17
70
  },
18
71
  },
19
- args: {},
72
+ args: {
73
+ title: "Find Kinetic Near You",
74
+ description: "Check availability in your area.",
75
+ background: "white",
76
+ color: "dark",
77
+ list: LOCATION_LIST,
78
+ },
20
79
  };
21
80
  export default meta;
22
81
  type Story = StoryObj<typeof meta>;
23
82
  export const Default: Story = {};
83
+
84
+ export const BackgroundWhite: Story = {
85
+ args: {
86
+ background: "white",
87
+ },
88
+ };
89
+
90
+ export const BackgroundBlue: Story = {
91
+ args: {
92
+ background: "blue",
93
+ },
94
+ };
95
+
96
+ export const BackgroundGreen: Story = {
97
+ args: {
98
+ background: "green",
99
+ },
100
+ };
101
+
102
+ export const BackgroundNavy: Story = {
103
+ args: {
104
+ background: "navy",
105
+ color: "light",
106
+ },
107
+ };
108
+
109
+ export const BackgroundPurple: Story = {
110
+ args: {
111
+ background: "purple",
112
+ color: "light",
113
+ },
114
+ };
115
+
116
+ export const BackgroundYellow: Story = {
117
+ args: {
118
+ background: "yellow",
119
+ },
120
+ };
121
+
122
+ export const WithImage: Story = {
123
+ args: {
124
+ image:
125
+ "https://images.ctfassets.net/8d4yn2ywtegc/1toxzgRb5VaVpdpsLWromO/8e5a9222d726b52a498eeea1340edd8c/AdobeStock_345768996.jpeg",
126
+ },
127
+ };
128
+
129
+ export const LightText: Story = {
130
+ args: {
131
+ background: "navy",
132
+ color: "light",
133
+ },
134
+ };
135
+
136
+ export const WithoutList: Story = {
137
+ args: {
138
+ list: [],
139
+ },
140
+ };