@windstream/react-shared-components 0.0.1

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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +360 -0
  3. package/dist/core.d.ts +389 -0
  4. package/dist/index.d.ts +389 -0
  5. package/dist/index.esm.js +35 -0
  6. package/dist/index.esm.js.map +1 -0
  7. package/dist/index.js +35 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/styles.css +3 -0
  10. package/dist/utils/index.d.ts +11 -0
  11. package/dist/utils/index.esm.js +2 -0
  12. package/dist/utils/index.esm.js.map +1 -0
  13. package/dist/utils/index.js +2 -0
  14. package/dist/utils/index.js.map +1 -0
  15. package/package.json +151 -0
  16. package/src/components/accordion/Accordion.stories.tsx +225 -0
  17. package/src/components/accordion/index.tsx +36 -0
  18. package/src/components/accordion/types.ts +10 -0
  19. package/src/components/alert-card/AlertCard.stories.tsx +172 -0
  20. package/src/components/alert-card/index.tsx +35 -0
  21. package/src/components/alert-card/types.ts +9 -0
  22. package/src/components/brand-button/BrandButton.stories.tsx +221 -0
  23. package/src/components/brand-button/helpers.ts +35 -0
  24. package/src/components/brand-button/index.tsx +90 -0
  25. package/src/components/brand-button/types.ts +27 -0
  26. package/src/components/button/Button.stories.tsx +108 -0
  27. package/src/components/button/index.tsx +23 -0
  28. package/src/components/button/types.ts +14 -0
  29. package/src/components/call-button/CallButton.stories.tsx +324 -0
  30. package/src/components/call-button/index.tsx +80 -0
  31. package/src/components/call-button/types.ts +9 -0
  32. package/src/components/checkbox/Checkbox.stories.tsx +248 -0
  33. package/src/components/checkbox/index.tsx +185 -0
  34. package/src/components/checkbox/types.ts +24 -0
  35. package/src/components/checklist/Checklist.stories.tsx +151 -0
  36. package/src/components/checklist/index.tsx +33 -0
  37. package/src/components/checklist/types.ts +6 -0
  38. package/src/components/collapse/Collapse.stories.tsx +256 -0
  39. package/src/components/collapse/index.tsx +44 -0
  40. package/src/components/collapse/types.ts +6 -0
  41. package/src/components/divider/Divider.stories.tsx +206 -0
  42. package/src/components/divider/index.tsx +23 -0
  43. package/src/components/divider/type.ts +3 -0
  44. package/src/components/input/Input.stories.tsx +358 -0
  45. package/src/components/input/index.tsx +174 -0
  46. package/src/components/input/types.ts +37 -0
  47. package/src/components/link/Link.stories.tsx +163 -0
  48. package/src/components/link/index.tsx +96 -0
  49. package/src/components/link/types.ts +25 -0
  50. package/src/components/list/List.stories.tsx +272 -0
  51. package/src/components/list/index.tsx +86 -0
  52. package/src/components/list/list-item/index.tsx +36 -0
  53. package/src/components/list/list-item/types.ts +13 -0
  54. package/src/components/list/types.ts +29 -0
  55. package/src/components/material-icon/MaterialIcon.stories.tsx +330 -0
  56. package/src/components/material-icon/constants.ts +95 -0
  57. package/src/components/material-icon/index.tsx +44 -0
  58. package/src/components/material-icon/types.ts +31 -0
  59. package/src/components/modal/Modal.stories.tsx +171 -0
  60. package/src/components/modal/index.tsx +168 -0
  61. package/src/components/modal/types.ts +24 -0
  62. package/src/components/radio-button/RadioButton.stories.tsx +307 -0
  63. package/src/components/radio-button/index.tsx +73 -0
  64. package/src/components/radio-button/types.ts +22 -0
  65. package/src/components/see-more/SeeMore.stories.tsx +182 -0
  66. package/src/components/see-more/index.tsx +38 -0
  67. package/src/components/see-more/types.ts +4 -0
  68. package/src/components/select/Select.stories.tsx +410 -0
  69. package/src/components/select/index.tsx +150 -0
  70. package/src/components/select/types.ts +34 -0
  71. package/src/components/select-plan-button/SelectPlanButton.stories.tsx +160 -0
  72. package/src/components/select-plan-button/index.tsx +21 -0
  73. package/src/components/select-plan-button/types.ts +4 -0
  74. package/src/components/skeleton/Skeleton.stories.tsx +180 -0
  75. package/src/components/skeleton/index.tsx +61 -0
  76. package/src/components/skeleton/types.ts +4 -0
  77. package/src/components/spinner/Spinner.stories.tsx +335 -0
  78. package/src/components/spinner/index.tsx +44 -0
  79. package/src/components/spinner/types.ts +5 -0
  80. package/src/components/text/Text.stories.tsx +302 -0
  81. package/src/components/text/index.tsx +26 -0
  82. package/src/components/text/types.ts +45 -0
  83. package/src/components/tooltip/Tooltip.stories.tsx +220 -0
  84. package/src/components/tooltip/index.tsx +78 -0
  85. package/src/components/tooltip/types.ts +7 -0
  86. package/src/components/view-cart-button/ViewCartButton.stories.tsx +241 -0
  87. package/src/components/view-cart-button/index.tsx +39 -0
  88. package/src/components/view-cart-button/types.ts +5 -0
  89. package/src/hooks/use-body-scroll-lock.ts +31 -0
  90. package/src/index.ts +81 -0
  91. package/src/setupTests.ts +46 -0
  92. package/src/stories/DocsTemplate.tsx +26 -0
  93. package/src/stories/assets/accessibility.png +0 -0
  94. package/src/stories/assets/accessibility.svg +1 -0
  95. package/src/stories/assets/addon-library.png +0 -0
  96. package/src/stories/assets/assets.png +0 -0
  97. package/src/stories/assets/avif-test-image.avif +0 -0
  98. package/src/stories/assets/context.png +0 -0
  99. package/src/stories/assets/discord.svg +1 -0
  100. package/src/stories/assets/docs.png +0 -0
  101. package/src/stories/assets/figma-plugin.png +0 -0
  102. package/src/stories/assets/github.svg +1 -0
  103. package/src/stories/assets/share.png +0 -0
  104. package/src/stories/assets/styling.png +0 -0
  105. package/src/stories/assets/testing.png +0 -0
  106. package/src/stories/assets/theming.png +0 -0
  107. package/src/stories/assets/tutorials.svg +1 -0
  108. package/src/stories/assets/youtube.svg +1 -0
  109. package/src/styles/globals.css +50 -0
  110. package/src/types/global.d.ts +9 -0
  111. package/src/utils/index.ts +49 -0
  112. package/tailwind.config.cjs +524 -0
@@ -0,0 +1,174 @@
1
+ import {
2
+ FocusEventHandler,
3
+ forwardRef,
4
+ ForwardRefRenderFunction,
5
+ MouseEventHandler,
6
+ useCallback,
7
+ useState,
8
+ } from "react";
9
+ import { cx } from "@shared/utils";
10
+ import { MaterialIcon } from "@shared/components/material-icon";
11
+ import { Text } from "@shared/components/text";
12
+ import { InputFieldProps, InputProps } from "./types";
13
+
14
+ export const InputField: ForwardRefRenderFunction<
15
+ HTMLInputElement,
16
+ InputFieldProps
17
+ > = (props, ref) => {
18
+ const {
19
+ className,
20
+ size,
21
+ required,
22
+ placeholder,
23
+ label,
24
+ state,
25
+ errorText,
26
+ prefixIconName,
27
+ prefixIconFill = true,
28
+ suffixIconFill = false,
29
+ prefixIconSize = 24,
30
+ suffixIconName,
31
+ suffixIconSize = 24,
32
+ containerClassName,
33
+ prefixIconClassName,
34
+ loading,
35
+ hasError,
36
+ type,
37
+ // Filter out all non-DOM props
38
+ ...rest
39
+ } = props;
40
+
41
+ // Use hasError to override state if provided
42
+ const effectiveState = hasError ? "error" : state;
43
+
44
+ const sizeClasses = {
45
+ sm: "h-[48px] rounded-lg px-3 py-2",
46
+ slim: "h-12",
47
+ medium: "h-14",
48
+ large: "h-[60px]",
49
+ };
50
+
51
+ const [isHovered, setIsHovered] = useState(false);
52
+ const [isFocused, setIsFocused] = useState(false);
53
+ const [inputType, setInputType] = useState(type || "text");
54
+
55
+ const handleMouseOver = () => setIsHovered(true);
56
+ const handleMouseOut = () => setIsHovered(false);
57
+ const handleFocus: FocusEventHandler<HTMLInputElement> = e => {
58
+ rest.onFocus?.(e);
59
+ setIsFocused(true);
60
+ };
61
+ const handleBlur: FocusEventHandler<HTMLInputElement> = e => {
62
+ rest.onBlur?.(e);
63
+ setIsFocused(false);
64
+ };
65
+
66
+ const togglePasswordVisibility = useCallback<
67
+ MouseEventHandler<HTMLDivElement>
68
+ >(e => {
69
+ e.stopPropagation();
70
+ setInputType(prevType =>
71
+ prevType === "password" ? "text" : "password"
72
+ );
73
+ }, []);
74
+
75
+ return (
76
+ <div className="relative">
77
+ {label ? (
78
+ <label htmlFor={label} className={cx("inline-block pb-1")}>
79
+ <Text as="span" className="footnote text-text">
80
+ {label}
81
+ {required ? (
82
+ <span className="pl-1 text-text-critical">*</span>
83
+ ) : null}
84
+ </Text>
85
+ </label>
86
+ ) : null}
87
+
88
+ <div
89
+ className={cx(
90
+ "relative flex w-80 flex-row items-center rounded-xl border border-input-border bg-input-bg-surface overflow-hidden",
91
+ sizeClasses[size || "medium"],
92
+ containerClassName,
93
+ (isHovered || effectiveState === "hover") && !isFocused && effectiveState !== "error" && "border-input-border-hover",
94
+ (isFocused ||
95
+ effectiveState === "focus" ||
96
+ effectiveState === "active") &&
97
+ "border-input-border-selected outline outline-1 outline-input-border-selected",
98
+ effectiveState === "error" && "border-input-border-critical outline outline-1 outline-input-border-critical"
99
+ )}
100
+ >
101
+ {prefixIconName ? (
102
+ <div
103
+ className="mr-1"
104
+ style={{
105
+ width: prefixIconSize,
106
+ height: prefixIconSize,
107
+ overflow: "hidden",
108
+ }}
109
+ >
110
+ <MaterialIcon
111
+ name={prefixIconName}
112
+ fill={prefixIconFill ? 1 : 0}
113
+ size={prefixIconSize}
114
+ className={cx(prefixIconClassName)}
115
+ />
116
+ </div>
117
+ ) : null}
118
+ <input
119
+ ref={ref}
120
+ id={label}
121
+ disabled={loading || rest.disabled}
122
+ className={cx(
123
+ "h-full grow rounded-xl p-4 caret-bg-inverse placeholder:text-input-text-placeholder focus:border-none focus:outline-none",
124
+ "text-body2",
125
+ size === "slim" && "text-body3",
126
+ className
127
+ )}
128
+ placeholder={placeholder}
129
+ type={inputType}
130
+ {...rest}
131
+ onMouseOver={handleMouseOver}
132
+ onMouseOut={handleMouseOut}
133
+ onFocus={handleFocus}
134
+ onBlur={handleBlur}
135
+ />
136
+ {suffixIconName ? (
137
+ <div
138
+ className="ml-2"
139
+ style={{
140
+ width: suffixIconSize,
141
+ height: suffixIconSize,
142
+ overflow: "hidden",
143
+ }}
144
+ onClick={togglePasswordVisibility}
145
+ >
146
+ <MaterialIcon
147
+ name={inputType === "text" ? "visibility" : "visibility_off"}
148
+ size={suffixIconSize}
149
+ fill={suffixIconFill ? 1 : 0}
150
+ />
151
+ </div>
152
+ ) : null}
153
+ </div>
154
+ {effectiveState === "error" && errorText && errorText != "" ? (
155
+ <Text
156
+ className={cx(
157
+ "footnote left-0 top-full inline-flex items-center pt-1 text-text-critical"
158
+ )}
159
+ >
160
+ <MaterialIcon name="info" size={20} className="mr-1" />
161
+ {errorText || "Error"}
162
+ </Text>
163
+ ) : null}
164
+ </div>
165
+ );
166
+ };
167
+
168
+ export const Input = forwardRef<HTMLInputElement, InputFieldProps>(
169
+ InputField
170
+ );
171
+
172
+ Input.displayName = "Input";
173
+
174
+ export type { InputFieldProps, InputProps };
@@ -0,0 +1,37 @@
1
+ import { InputHTMLAttributes, ReactNode } from "react";
2
+
3
+ export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size"> & {
4
+ loading?: boolean;
5
+ iconLeft?: ReactNode;
6
+ size?: "sm" | "md";
7
+ hasError?: boolean;
8
+ label?: string;
9
+ labelClassName?: string;
10
+ containerClassName?: string;
11
+ mapPinIcon?: boolean;
12
+ isLightMapPinIcon?: boolean;
13
+ passwordToggle?: boolean;
14
+ helperText?: string;
15
+ error?: string;
16
+ variant?: "default" | "unstyled";
17
+ };
18
+
19
+ export type InputFieldProps = Omit<
20
+ InputHTMLAttributes<HTMLInputElement>,
21
+ "size"
22
+ > & {
23
+ state?: "default" | "focus" | "active" | "hover" | "filled" | "error";
24
+ size?: "slim" | "medium" | "large";
25
+ label?: string;
26
+ errorText?: string;
27
+ prefixIconName?: "location_on" | "search";
28
+ prefixIconSize?: 20 | 24 | 40 | 48;
29
+ prefixIconFill?: boolean;
30
+ suffixIconFill?: boolean;
31
+ suffixIconName?: "visibility" | "visibility_off" | "lock";
32
+ suffixIconSize?: 20 | 24 | 40 | 48;
33
+ containerClassName?: string;
34
+ prefixIconClassName?: string;
35
+ loading?: boolean;
36
+ hasError?: boolean;
37
+ };
@@ -0,0 +1,163 @@
1
+ import { Link } from "./index";
2
+ import { DocsPage } from "@shared/stories/DocsTemplate";
3
+
4
+ import type { Meta, StoryObj } from "@storybook/react";
5
+
6
+ const meta: Meta<typeof Link> = {
7
+ title: "Components/Link",
8
+ component: Link,
9
+ tags: ["autodocs"],
10
+ parameters: {
11
+ layout: "centered",
12
+ docs: {
13
+ page: DocsPage,
14
+ description: {
15
+ component:
16
+ "A flexible link component with multiple variants and states.",
17
+ },
18
+ },
19
+ },
20
+ argTypes: {
21
+ variant: {
22
+ control: { type: "select" },
23
+ options: ["default", "unstyled"],
24
+ description: "Link styling variant",
25
+ },
26
+ href: {
27
+ control: { type: "text" },
28
+ description: "Link URL",
29
+ },
30
+ target: {
31
+ control: { type: "select" },
32
+ options: ["_self", "_blank", "_parent", "_top"],
33
+ description: "Link target",
34
+ },
35
+ disabled: {
36
+ control: { type: "boolean" },
37
+ description: "Disable the link",
38
+ },
39
+ children: {
40
+ control: { type: "text" },
41
+ description: "Link content",
42
+ },
43
+ },
44
+ args: {
45
+ children: "Link Text",
46
+ href: "#",
47
+ variant: "default",
48
+ target: "_self",
49
+ disabled: false,
50
+ },
51
+ };
52
+
53
+ export default meta;
54
+ type Story = StoryObj<typeof meta>;
55
+
56
+ // Default link
57
+ export const Default: Story = {
58
+ args: {},
59
+ };
60
+
61
+ // Unstyled link
62
+ export const Unstyled: Story = {
63
+ args: {
64
+ variant: "unstyled",
65
+ children: "Unstyled Link",
66
+ },
67
+ };
68
+
69
+ // External link
70
+ export const External: Story = {
71
+ args: {
72
+ href: "https://example.com",
73
+ target: "_blank",
74
+ children: "External Link ↗",
75
+ },
76
+ };
77
+
78
+ // Disabled link
79
+ export const Disabled: Story = {
80
+ args: {
81
+ disabled: true,
82
+ children: "Disabled Link",
83
+ },
84
+ };
85
+
86
+ // Link with icon
87
+ export const WithIcon: Story = {
88
+ args: {
89
+ children: "Link with Icon →",
90
+ className: "flex items-center gap-2",
91
+ },
92
+ };
93
+
94
+ // Navigation links
95
+ export const Navigation: Story = {
96
+ render: () => (
97
+ <nav className="flex gap-6">
98
+ <Link href="#home" variant="default">
99
+ Home
100
+ </Link>
101
+ <Link href="#about">About</Link>
102
+ <Link href="#services">Services</Link>
103
+ <Link href="#contact">Contact</Link>
104
+ </nav>
105
+ ),
106
+ parameters: {
107
+ docs: {
108
+ description: {
109
+ story: "Navigation menu with different link variants.",
110
+ },
111
+ },
112
+ },
113
+ };
114
+
115
+ // All variants showcase
116
+ export const AllVariants: Story = {
117
+ render: () => (
118
+ <div className="space-y-6">
119
+ <div>
120
+ <h3 className="mb-3 text-lg font-semibold">Variants</h3>
121
+ <div className="flex gap-4">
122
+ <Link href="#" variant="default">
123
+ Default
124
+ </Link>
125
+ <Link href="#" variant="unstyled">
126
+ Unstyled
127
+ </Link>
128
+ </div>
129
+ </div>
130
+
131
+ <div>
132
+ <h3 className="mb-3 text-lg font-semibold">Variants</h3>
133
+ <div className="flex items-center gap-4">
134
+ <Link href="#" variant="default">
135
+ Default
136
+ </Link>
137
+ <Link href="#" variant="unstyled">
138
+ Unstyled
139
+ </Link>
140
+ </div>
141
+ </div>
142
+
143
+ <div>
144
+ <h3 className="mb-3 text-lg font-semibold">States</h3>
145
+ <div className="flex gap-4">
146
+ <Link href="#" disabled>
147
+ Disabled
148
+ </Link>
149
+ <Link href="https://example.com" target="_blank">
150
+ External ↗
151
+ </Link>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ ),
156
+ parameters: {
157
+ docs: {
158
+ description: {
159
+ story: "Showcase of all link variants, sizes, and states.",
160
+ },
161
+ },
162
+ },
163
+ };
@@ -0,0 +1,96 @@
1
+ import React, { forwardRef } from "react";
2
+ import { cx } from "../../utils";
3
+ import { LinkProps } from "./types";
4
+
5
+ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
6
+ (
7
+ {
8
+ children,
9
+ href,
10
+ className = "",
11
+ onClick,
12
+ variant = "unstyled",
13
+ style,
14
+ external = false,
15
+ disabled = false,
16
+ ...props
17
+ },
18
+ ref
19
+ ) => {
20
+ // Get Tailwind classes for different variants
21
+ const getVariantClasses = () => {
22
+ if (variant === "unstyled") return "";
23
+
24
+ // TODO: Add styles based on the figma design for all variants
25
+ const baseClasses =
26
+ "transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2";
27
+
28
+ const variantClasses = {
29
+ default:
30
+ "text-text underline",
31
+ };
32
+
33
+ const stateClasses = [
34
+ disabled
35
+ ? "opacity-60 cursor-not-allowed pointer-events-none"
36
+ : "cursor-pointer",
37
+ ]
38
+ .filter(Boolean)
39
+ .join(" ");
40
+
41
+ return [
42
+ baseClasses,
43
+ variantClasses[variant as keyof typeof variantClasses] ||
44
+ variantClasses.default,
45
+ stateClasses,
46
+ ]
47
+ .filter(Boolean)
48
+ .join(" ");
49
+ };
50
+
51
+ const tailwindClasses = getVariantClasses();
52
+
53
+ // Handle click events
54
+ const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
55
+ if (disabled) {
56
+ event.preventDefault();
57
+ return;
58
+ }
59
+
60
+ onClick?.(event);
61
+ };
62
+
63
+ // Combine all classes
64
+ const combinedClassName = cx(
65
+ tailwindClasses,
66
+ `link--${variant}`,
67
+ disabled && "link--disabled",
68
+ className
69
+ );
70
+
71
+ // Determine link props based on external/internal
72
+ const linkProps = {
73
+ ...props,
74
+ ref,
75
+ className: combinedClassName,
76
+ style,
77
+ href: disabled ? undefined : href,
78
+ onClick: handleClick,
79
+ ...(external &&
80
+ !disabled && {
81
+ target: "_blank",
82
+ rel: "noopener noreferrer",
83
+ }),
84
+ ...(disabled && {
85
+ "aria-disabled": true,
86
+ tabIndex: -1,
87
+ }),
88
+ };
89
+
90
+ return <a {...linkProps}>{children}</a>;
91
+ }
92
+ );
93
+
94
+ Link.displayName = "Link";
95
+
96
+ export type { LinkProps };
@@ -0,0 +1,25 @@
1
+ import {
2
+ type AnchorHTMLAttributes,
3
+ type CSSProperties,
4
+ type MouseEvent,
5
+ type ReactNode,
6
+ } from "react";
7
+
8
+ export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
9
+ /** Content to render inside the link */
10
+ children: ReactNode;
11
+ /** URL to navigate to */
12
+ href?: string;
13
+ /** Custom className for the link */
14
+ className?: string;
15
+ /** Click handler function */
16
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
17
+ /** Additional styling options */
18
+ variant?: "default" | "unstyled";
19
+ /** Custom styles */
20
+ style?: CSSProperties;
21
+ /** External link behavior - opens in new tab */
22
+ external?: boolean;
23
+ /** Disable the link */
24
+ disabled?: boolean;
25
+ }