@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.
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/dist/core.d.ts +389 -0
- package/dist/index.d.ts +389 -0
- package/dist/index.esm.js +35 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +3 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.esm.js +2 -0
- package/dist/utils/index.esm.js.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +151 -0
- package/src/components/accordion/Accordion.stories.tsx +225 -0
- package/src/components/accordion/index.tsx +36 -0
- package/src/components/accordion/types.ts +10 -0
- package/src/components/alert-card/AlertCard.stories.tsx +172 -0
- package/src/components/alert-card/index.tsx +35 -0
- package/src/components/alert-card/types.ts +9 -0
- package/src/components/brand-button/BrandButton.stories.tsx +221 -0
- package/src/components/brand-button/helpers.ts +35 -0
- package/src/components/brand-button/index.tsx +90 -0
- package/src/components/brand-button/types.ts +27 -0
- package/src/components/button/Button.stories.tsx +108 -0
- package/src/components/button/index.tsx +23 -0
- package/src/components/button/types.ts +14 -0
- package/src/components/call-button/CallButton.stories.tsx +324 -0
- package/src/components/call-button/index.tsx +80 -0
- package/src/components/call-button/types.ts +9 -0
- package/src/components/checkbox/Checkbox.stories.tsx +248 -0
- package/src/components/checkbox/index.tsx +185 -0
- package/src/components/checkbox/types.ts +24 -0
- package/src/components/checklist/Checklist.stories.tsx +151 -0
- package/src/components/checklist/index.tsx +33 -0
- package/src/components/checklist/types.ts +6 -0
- package/src/components/collapse/Collapse.stories.tsx +256 -0
- package/src/components/collapse/index.tsx +44 -0
- package/src/components/collapse/types.ts +6 -0
- package/src/components/divider/Divider.stories.tsx +206 -0
- package/src/components/divider/index.tsx +23 -0
- package/src/components/divider/type.ts +3 -0
- package/src/components/input/Input.stories.tsx +358 -0
- package/src/components/input/index.tsx +174 -0
- package/src/components/input/types.ts +37 -0
- package/src/components/link/Link.stories.tsx +163 -0
- package/src/components/link/index.tsx +96 -0
- package/src/components/link/types.ts +25 -0
- package/src/components/list/List.stories.tsx +272 -0
- package/src/components/list/index.tsx +86 -0
- package/src/components/list/list-item/index.tsx +36 -0
- package/src/components/list/list-item/types.ts +13 -0
- package/src/components/list/types.ts +29 -0
- package/src/components/material-icon/MaterialIcon.stories.tsx +330 -0
- package/src/components/material-icon/constants.ts +95 -0
- package/src/components/material-icon/index.tsx +44 -0
- package/src/components/material-icon/types.ts +31 -0
- package/src/components/modal/Modal.stories.tsx +171 -0
- package/src/components/modal/index.tsx +168 -0
- package/src/components/modal/types.ts +24 -0
- package/src/components/radio-button/RadioButton.stories.tsx +307 -0
- package/src/components/radio-button/index.tsx +73 -0
- package/src/components/radio-button/types.ts +22 -0
- package/src/components/see-more/SeeMore.stories.tsx +182 -0
- package/src/components/see-more/index.tsx +38 -0
- package/src/components/see-more/types.ts +4 -0
- package/src/components/select/Select.stories.tsx +410 -0
- package/src/components/select/index.tsx +150 -0
- package/src/components/select/types.ts +34 -0
- package/src/components/select-plan-button/SelectPlanButton.stories.tsx +160 -0
- package/src/components/select-plan-button/index.tsx +21 -0
- package/src/components/select-plan-button/types.ts +4 -0
- package/src/components/skeleton/Skeleton.stories.tsx +180 -0
- package/src/components/skeleton/index.tsx +61 -0
- package/src/components/skeleton/types.ts +4 -0
- package/src/components/spinner/Spinner.stories.tsx +335 -0
- package/src/components/spinner/index.tsx +44 -0
- package/src/components/spinner/types.ts +5 -0
- package/src/components/text/Text.stories.tsx +302 -0
- package/src/components/text/index.tsx +26 -0
- package/src/components/text/types.ts +45 -0
- package/src/components/tooltip/Tooltip.stories.tsx +220 -0
- package/src/components/tooltip/index.tsx +78 -0
- package/src/components/tooltip/types.ts +7 -0
- package/src/components/view-cart-button/ViewCartButton.stories.tsx +241 -0
- package/src/components/view-cart-button/index.tsx +39 -0
- package/src/components/view-cart-button/types.ts +5 -0
- package/src/hooks/use-body-scroll-lock.ts +31 -0
- package/src/index.ts +81 -0
- package/src/setupTests.ts +46 -0
- package/src/stories/DocsTemplate.tsx +26 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/styles/globals.css +50 -0
- package/src/types/global.d.ts +9 -0
- package/src/utils/index.ts +49 -0
- package/tailwind.config.cjs +524 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import { TextProps, TextVariant } from "./types";
|
|
3
|
+
|
|
4
|
+
export const Text = forwardRef<HTMLElement, TextProps>(
|
|
5
|
+
(
|
|
6
|
+
{ children, as, className = "", style, ...props },
|
|
7
|
+
ref
|
|
8
|
+
) => {
|
|
9
|
+
const elementType = as || 'p';
|
|
10
|
+
|
|
11
|
+
return React.createElement(
|
|
12
|
+
elementType,
|
|
13
|
+
{
|
|
14
|
+
ref,
|
|
15
|
+
className,
|
|
16
|
+
style,
|
|
17
|
+
...props,
|
|
18
|
+
},
|
|
19
|
+
children
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
Text.displayName = "Text";
|
|
25
|
+
|
|
26
|
+
export type { TextProps, TextVariant };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type CSSProperties, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type TextVariant =
|
|
4
|
+
| "heading1"
|
|
5
|
+
| "heading2"
|
|
6
|
+
| "heading3"
|
|
7
|
+
| "heading4"
|
|
8
|
+
| "heading5"
|
|
9
|
+
| "heading6"
|
|
10
|
+
| "subheading1"
|
|
11
|
+
| "subheading2"
|
|
12
|
+
| "subheading3"
|
|
13
|
+
| "subheading4"
|
|
14
|
+
| "subheading5"
|
|
15
|
+
| "subheading6"
|
|
16
|
+
| "body1"
|
|
17
|
+
| "body2"
|
|
18
|
+
| "body3"
|
|
19
|
+
| "footnote"
|
|
20
|
+
| "micro"
|
|
21
|
+
| "label1"
|
|
22
|
+
| "label2"
|
|
23
|
+
| "label3"
|
|
24
|
+
| "label4"
|
|
25
|
+
| "error"
|
|
26
|
+
| "unstyled";
|
|
27
|
+
export type TextWeight =
|
|
28
|
+
| "thin"
|
|
29
|
+
| "normal"
|
|
30
|
+
| "medium"
|
|
31
|
+
| "semibold"
|
|
32
|
+
| "bold"
|
|
33
|
+
| "extrabold"
|
|
34
|
+
| "black"
|
|
35
|
+
| undefined;
|
|
36
|
+
export interface TextProps extends HTMLAttributes<HTMLElement> {
|
|
37
|
+
/** Content to render inside the text element */
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
/** Custom HTML element to render as */
|
|
40
|
+
as?: keyof import("react").JSX.IntrinsicElements;
|
|
41
|
+
/** Custom className for the text */
|
|
42
|
+
className?: string;
|
|
43
|
+
/** Custom styles */
|
|
44
|
+
style?: CSSProperties;
|
|
45
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Tooltip } from "./index";
|
|
2
|
+
import { DocsPage } from "@shared/stories/DocsTemplate";
|
|
3
|
+
|
|
4
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Tooltip> = {
|
|
7
|
+
title: "Components/Tooltip",
|
|
8
|
+
component: Tooltip,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: "centered",
|
|
12
|
+
docs: {
|
|
13
|
+
page: DocsPage,
|
|
14
|
+
description: {
|
|
15
|
+
component:
|
|
16
|
+
"A tooltip component that displays additional information when hovering over an element.",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
argTypes: {
|
|
21
|
+
tooltipMsg: {
|
|
22
|
+
control: { type: "text" },
|
|
23
|
+
description: "Tooltip message to display",
|
|
24
|
+
},
|
|
25
|
+
className: {
|
|
26
|
+
control: { type: "text" },
|
|
27
|
+
description: "Additional CSS classes",
|
|
28
|
+
},
|
|
29
|
+
children: {
|
|
30
|
+
control: { type: "text" },
|
|
31
|
+
description: "Element that triggers the tooltip",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
args: {
|
|
35
|
+
tooltipMsg: "This is a tooltip message",
|
|
36
|
+
children: "Hover me",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof meta>;
|
|
42
|
+
|
|
43
|
+
// Default tooltip
|
|
44
|
+
export const Default: Story = {
|
|
45
|
+
args: {},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// With message
|
|
49
|
+
export const WithMessage: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
tooltipMsg: "This is a helpful tooltip message",
|
|
52
|
+
children: "Hover to see tooltip",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// With button
|
|
57
|
+
export const WithButton: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
tooltipMsg: "Click to submit the form",
|
|
60
|
+
children: (
|
|
61
|
+
<button className="px-4 py-2 bg-fill-brand text-inverse rounded">
|
|
62
|
+
Submit
|
|
63
|
+
</button>
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// With icon
|
|
69
|
+
export const WithIcon: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
tooltipMsg: "More information",
|
|
72
|
+
children: (
|
|
73
|
+
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-surface-secondary text-icon-info cursor-help">
|
|
74
|
+
i
|
|
75
|
+
</span>
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// With link
|
|
81
|
+
export const WithLink: Story = {
|
|
82
|
+
args: {
|
|
83
|
+
tooltipMsg: "Visit our documentation",
|
|
84
|
+
children: (
|
|
85
|
+
<a
|
|
86
|
+
href="#"
|
|
87
|
+
className="text-text-brand underline hover:text-text-brand-hover"
|
|
88
|
+
>
|
|
89
|
+
Learn more
|
|
90
|
+
</a>
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Long message
|
|
96
|
+
export const LongMessage: Story = {
|
|
97
|
+
args: {
|
|
98
|
+
tooltipMsg:
|
|
99
|
+
"This is a longer tooltip message that demonstrates how the tooltip handles extended text content. It should wrap appropriately to remain readable.",
|
|
100
|
+
children: "Hover for long message",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Different children
|
|
105
|
+
export const DifferentChildren: Story = {
|
|
106
|
+
render: () => (
|
|
107
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
108
|
+
<Tooltip tooltipMsg="Text tooltip">
|
|
109
|
+
<span className="underline cursor-help">Text</span>
|
|
110
|
+
</Tooltip>
|
|
111
|
+
<Tooltip tooltipMsg="Button tooltip">
|
|
112
|
+
<button className="px-4 py-2 bg-fill-brand text-inverse rounded">
|
|
113
|
+
Button
|
|
114
|
+
</button>
|
|
115
|
+
</Tooltip>
|
|
116
|
+
<Tooltip tooltipMsg="Icon tooltip">
|
|
117
|
+
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-surface-secondary text-icon-info cursor-help">
|
|
118
|
+
?
|
|
119
|
+
</span>
|
|
120
|
+
</Tooltip>
|
|
121
|
+
<Tooltip tooltipMsg="Link tooltip">
|
|
122
|
+
<a href="#" className="text-text-brand underline">
|
|
123
|
+
Link
|
|
124
|
+
</a>
|
|
125
|
+
</Tooltip>
|
|
126
|
+
</div>
|
|
127
|
+
),
|
|
128
|
+
parameters: {
|
|
129
|
+
docs: {
|
|
130
|
+
description: {
|
|
131
|
+
story: "Tooltip with different types of children elements.",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Custom styling
|
|
138
|
+
export const CustomStyling: Story = {
|
|
139
|
+
args: {
|
|
140
|
+
tooltipMsg: "Custom styled tooltip",
|
|
141
|
+
className: "custom-tooltip",
|
|
142
|
+
children: "Hover me",
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Multiple tooltips
|
|
147
|
+
export const Multiple: Story = {
|
|
148
|
+
render: () => (
|
|
149
|
+
<div className="space-y-4">
|
|
150
|
+
<div className="flex gap-4">
|
|
151
|
+
<Tooltip tooltipMsg="First tooltip">
|
|
152
|
+
<button className="px-4 py-2 bg-surface-secondary text-text rounded">
|
|
153
|
+
Button 1
|
|
154
|
+
</button>
|
|
155
|
+
</Tooltip>
|
|
156
|
+
<Tooltip tooltipMsg="Second tooltip">
|
|
157
|
+
<button className="px-4 py-2 bg-surface-secondary text-text rounded">
|
|
158
|
+
Button 2
|
|
159
|
+
</button>
|
|
160
|
+
</Tooltip>
|
|
161
|
+
<Tooltip tooltipMsg="Third tooltip">
|
|
162
|
+
<button className="px-4 py-2 bg-surface-secondary text-text rounded">
|
|
163
|
+
Button 3
|
|
164
|
+
</button>
|
|
165
|
+
</Tooltip>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
),
|
|
169
|
+
parameters: {
|
|
170
|
+
docs: {
|
|
171
|
+
description: {
|
|
172
|
+
story: "Multiple tooltips displayed together.",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// All variants showcase
|
|
179
|
+
export const AllVariants: Story = {
|
|
180
|
+
render: () => (
|
|
181
|
+
<div className="w-full max-w-2xl space-y-8">
|
|
182
|
+
<div>
|
|
183
|
+
<h3 className="mb-3 text-lg font-semibold">With Text</h3>
|
|
184
|
+
<Tooltip tooltipMsg="This is a tooltip message">
|
|
185
|
+
<span className="underline cursor-help">Hover me</span>
|
|
186
|
+
</Tooltip>
|
|
187
|
+
</div>
|
|
188
|
+
<div>
|
|
189
|
+
<h3 className="mb-3 text-lg font-semibold">With Button</h3>
|
|
190
|
+
<Tooltip tooltipMsg="Click to submit">
|
|
191
|
+
<button className="px-4 py-2 bg-fill-brand text-inverse rounded">
|
|
192
|
+
Submit
|
|
193
|
+
</button>
|
|
194
|
+
</Tooltip>
|
|
195
|
+
</div>
|
|
196
|
+
<div>
|
|
197
|
+
<h3 className="mb-3 text-lg font-semibold">With Icon</h3>
|
|
198
|
+
<Tooltip tooltipMsg="More information">
|
|
199
|
+
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-surface-secondary text-icon-info cursor-help">
|
|
200
|
+
i
|
|
201
|
+
</span>
|
|
202
|
+
</Tooltip>
|
|
203
|
+
</div>
|
|
204
|
+
<div>
|
|
205
|
+
<h3 className="mb-3 text-lg font-semibold">Long Message</h3>
|
|
206
|
+
<Tooltip tooltipMsg="This is a longer tooltip message that demonstrates how the tooltip handles extended text content.">
|
|
207
|
+
<span className="underline cursor-help">Hover for long message</span>
|
|
208
|
+
</Tooltip>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
),
|
|
212
|
+
parameters: {
|
|
213
|
+
docs: {
|
|
214
|
+
description: {
|
|
215
|
+
story: "Showcase of all tooltip variants and use cases.",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ToolTipProps } from "./types";
|
|
2
|
+
|
|
3
|
+
export function Tooltip({
|
|
4
|
+
tooltipMsg,
|
|
5
|
+
className,
|
|
6
|
+
children,
|
|
7
|
+
}: ToolTipProps) {
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<style>{`
|
|
11
|
+
.tooltip-custom {
|
|
12
|
+
position: relative;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
margin-top: 7px;
|
|
15
|
+
}
|
|
16
|
+
.tooltip-custom::after {
|
|
17
|
+
display: none;
|
|
18
|
+
position: absolute;
|
|
19
|
+
text-align: center;
|
|
20
|
+
z-index: 999;
|
|
21
|
+
content: attr(data-text);
|
|
22
|
+
top: 0;
|
|
23
|
+
left: -200%;
|
|
24
|
+
transform: translate(-50%, calc(-100% - 10px));
|
|
25
|
+
line-height: 1.1rem;
|
|
26
|
+
padding-left: 1rem;
|
|
27
|
+
padding-right: 1rem;
|
|
28
|
+
border-radius: 8px;
|
|
29
|
+
color: white;
|
|
30
|
+
background-color: black;
|
|
31
|
+
padding-bottom: 0.75rem;
|
|
32
|
+
padding-top: 0.75rem;
|
|
33
|
+
width: 270px;
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
}
|
|
36
|
+
.tooltip-custom:hover::after {
|
|
37
|
+
display: block;
|
|
38
|
+
}
|
|
39
|
+
.tooltip-custom::before {
|
|
40
|
+
background-color: black;
|
|
41
|
+
content: " ";
|
|
42
|
+
display: none;
|
|
43
|
+
position: absolute;
|
|
44
|
+
width: 15px;
|
|
45
|
+
height: 15px;
|
|
46
|
+
z-index: 999;
|
|
47
|
+
top: 0;
|
|
48
|
+
left: 50%;
|
|
49
|
+
transform: translate(-50%, calc(-100% - 5px)) rotate(45deg);
|
|
50
|
+
}
|
|
51
|
+
.tooltip-custom:hover::before {
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
@media (max-width: 640px) {
|
|
55
|
+
.tooltip-custom::after {
|
|
56
|
+
left: -124px;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
@media (max-width: 343px) {
|
|
60
|
+
.tooltip-custom::after {
|
|
61
|
+
left: 80px;
|
|
62
|
+
width: 160px;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`}</style>
|
|
66
|
+
<span
|
|
67
|
+
className={`tooltip-custom ${className || ""}`}
|
|
68
|
+
data-text={tooltipMsg}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</span>
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Tooltip.displayName = "Tooltip";
|
|
77
|
+
|
|
78
|
+
export type { ToolTipProps };
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { ViewCartButton } from "./index";
|
|
2
|
+
import { DocsPage } from "@shared/stories/DocsTemplate";
|
|
3
|
+
|
|
4
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof ViewCartButton> = {
|
|
8
|
+
title: "Components/ViewCartButton",
|
|
9
|
+
component: ViewCartButton,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: "centered",
|
|
13
|
+
docs: {
|
|
14
|
+
page: DocsPage,
|
|
15
|
+
description: {
|
|
16
|
+
component:
|
|
17
|
+
"A button component for viewing cart contents with total price display and open/closed states.",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
argTypes: {
|
|
22
|
+
cartTotalText: {
|
|
23
|
+
control: { type: "text" },
|
|
24
|
+
description: "Text displaying the cart total",
|
|
25
|
+
},
|
|
26
|
+
isOpen: {
|
|
27
|
+
control: { type: "boolean" },
|
|
28
|
+
description: "Whether the cart is open",
|
|
29
|
+
},
|
|
30
|
+
onClick: {
|
|
31
|
+
action: "clicked",
|
|
32
|
+
description: "Callback function when button is clicked",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
args: {
|
|
36
|
+
cartTotalText: "$99.99",
|
|
37
|
+
isOpen: false,
|
|
38
|
+
onClick: () => {},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
type Story = StoryObj<typeof meta>;
|
|
44
|
+
|
|
45
|
+
// Default button
|
|
46
|
+
export const Default: Story = {
|
|
47
|
+
args: {},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// With cart total
|
|
51
|
+
export const WithCartTotal: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
cartTotalText: "$149.99",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Open state
|
|
58
|
+
export const Open: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
cartTotalText: "$99.99",
|
|
61
|
+
isOpen: true,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Closed state
|
|
66
|
+
export const Closed: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
cartTotalText: "$99.99",
|
|
69
|
+
isOpen: false,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Interactive state
|
|
74
|
+
export const Interactive: Story = {
|
|
75
|
+
render: () => {
|
|
76
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
77
|
+
return (
|
|
78
|
+
<div className="w-full max-w-md">
|
|
79
|
+
<ViewCartButton
|
|
80
|
+
cartTotalText="$149.99"
|
|
81
|
+
isOpen={isOpen}
|
|
82
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
83
|
+
/>
|
|
84
|
+
{isOpen && (
|
|
85
|
+
<div className="mt-4 p-4 bg-surface-secondary rounded">
|
|
86
|
+
<p className="body2 text-text">Cart is open</p>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
parameters: {
|
|
93
|
+
docs: {
|
|
94
|
+
description: {
|
|
95
|
+
story:
|
|
96
|
+
"Interactive ViewCartButton that toggles open/closed state on click.",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Different totals
|
|
103
|
+
export const DifferentTotals: Story = {
|
|
104
|
+
render: () => (
|
|
105
|
+
<div className="w-full max-w-md space-y-3">
|
|
106
|
+
<ViewCartButton cartTotalText="$0.00" isOpen={false} onClick={() => {}} />
|
|
107
|
+
<ViewCartButton cartTotalText="$29.99" isOpen={false} onClick={() => {}} />
|
|
108
|
+
<ViewCartButton cartTotalText="$99.99" isOpen={false} onClick={() => {}} />
|
|
109
|
+
<ViewCartButton cartTotalText="$249.99" isOpen={false} onClick={() => {}} />
|
|
110
|
+
</div>
|
|
111
|
+
),
|
|
112
|
+
parameters: {
|
|
113
|
+
docs: {
|
|
114
|
+
description: {
|
|
115
|
+
story: "ViewCartButton with different cart total values.",
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Responsive behavior
|
|
122
|
+
export const Responsive: Story = {
|
|
123
|
+
render: () => (
|
|
124
|
+
<div className="space-y-8">
|
|
125
|
+
<div>
|
|
126
|
+
<h3 className="mb-2 text-sm font-medium text-gray-600">
|
|
127
|
+
Mobile View (btn-small)
|
|
128
|
+
</h3>
|
|
129
|
+
<div className="w-64 border border-gray-200 p-2">
|
|
130
|
+
<ViewCartButton
|
|
131
|
+
cartTotalText="$99.99"
|
|
132
|
+
isOpen={false}
|
|
133
|
+
onClick={() => {}}
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
<div>
|
|
138
|
+
<h3 className="mb-2 text-sm font-medium text-gray-600">
|
|
139
|
+
Desktop View (btn-medium)
|
|
140
|
+
</h3>
|
|
141
|
+
<div className="w-full max-w-md border border-gray-200 p-2">
|
|
142
|
+
<ViewCartButton
|
|
143
|
+
cartTotalText="$99.99"
|
|
144
|
+
isOpen={false}
|
|
145
|
+
onClick={() => {}}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
),
|
|
151
|
+
parameters: {
|
|
152
|
+
docs: {
|
|
153
|
+
description: {
|
|
154
|
+
story:
|
|
155
|
+
"ViewCartButton adapts its size responsively - smaller on mobile, larger on desktop.",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Open and closed states
|
|
162
|
+
export const OpenAndClosed: Story = {
|
|
163
|
+
render: () => (
|
|
164
|
+
<div className="w-full max-w-md space-y-4">
|
|
165
|
+
<div>
|
|
166
|
+
<h4 className="mb-2 text-sm font-medium">Closed State</h4>
|
|
167
|
+
<ViewCartButton
|
|
168
|
+
cartTotalText="$99.99"
|
|
169
|
+
isOpen={false}
|
|
170
|
+
onClick={() => {}}
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
<div>
|
|
174
|
+
<h4 className="mb-2 text-sm font-medium">Open State</h4>
|
|
175
|
+
<ViewCartButton
|
|
176
|
+
cartTotalText="$99.99"
|
|
177
|
+
isOpen={true}
|
|
178
|
+
onClick={() => {}}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
),
|
|
183
|
+
parameters: {
|
|
184
|
+
docs: {
|
|
185
|
+
description: {
|
|
186
|
+
story: "ViewCartButton in both open and closed states.",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// All variants showcase
|
|
193
|
+
export const AllVariants: Story = {
|
|
194
|
+
render: () => (
|
|
195
|
+
<div className="w-full max-w-2xl space-y-6">
|
|
196
|
+
<div>
|
|
197
|
+
<h3 className="mb-3 text-lg font-semibold">States</h3>
|
|
198
|
+
<div className="space-y-3">
|
|
199
|
+
<ViewCartButton
|
|
200
|
+
cartTotalText="$99.99"
|
|
201
|
+
isOpen={false}
|
|
202
|
+
onClick={() => {}}
|
|
203
|
+
/>
|
|
204
|
+
<ViewCartButton
|
|
205
|
+
cartTotalText="$99.99"
|
|
206
|
+
isOpen={true}
|
|
207
|
+
onClick={() => {}}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div>
|
|
212
|
+
<h3 className="mb-3 text-lg font-semibold">Different Totals</h3>
|
|
213
|
+
<div className="space-y-3">
|
|
214
|
+
<ViewCartButton
|
|
215
|
+
cartTotalText="$0.00"
|
|
216
|
+
isOpen={false}
|
|
217
|
+
onClick={() => {}}
|
|
218
|
+
/>
|
|
219
|
+
<ViewCartButton
|
|
220
|
+
cartTotalText="$29.99"
|
|
221
|
+
isOpen={false}
|
|
222
|
+
onClick={() => {}}
|
|
223
|
+
/>
|
|
224
|
+
<ViewCartButton
|
|
225
|
+
cartTotalText="$149.99"
|
|
226
|
+
isOpen={false}
|
|
227
|
+
onClick={() => {}}
|
|
228
|
+
/>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
),
|
|
233
|
+
parameters: {
|
|
234
|
+
docs: {
|
|
235
|
+
description: {
|
|
236
|
+
story: "Showcase of all ViewCartButton variants and states.",
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cx } from "@shared/utils";
|
|
2
|
+
import { Button } from "../button";
|
|
3
|
+
import { MaterialIcon } from "../material-icon";
|
|
4
|
+
import { Text } from "../text";
|
|
5
|
+
import { ViewCartButtonProps } from "./types";
|
|
6
|
+
const baseClasses =
|
|
7
|
+
"btn-medium rounded-2xl pl-15 pr-15 inline-flex gap-2 items-center justify-center outline-none focus:ring-2 focus:ring-offset-2 cursor-pointer transition-colors duration-200 align-top border-2 border-border-secondary-on-bg-fill bg-bg text-text focus:ring-bg-surface-inverse";
|
|
8
|
+
export const ViewCartButton: React.FC<ViewCartButtonProps> = (props) => {
|
|
9
|
+
const { cartTotalText, onClick, isOpen, ...rest } = props;
|
|
10
|
+
return (
|
|
11
|
+
<Button
|
|
12
|
+
className={cx(baseClasses, "btn-small w-full border-border pl-6 pr-4 md:btn-medium")}
|
|
13
|
+
onClick={onClick}
|
|
14
|
+
{...rest}
|
|
15
|
+
>
|
|
16
|
+
<div className="flex w-full items-center justify-center gap-2">
|
|
17
|
+
<div className="flex flex-col items-center justify-center">
|
|
18
|
+
<Text className="footnote -mb-1 text-text-secondary sm:body3">
|
|
19
|
+
View cart
|
|
20
|
+
</Text>
|
|
21
|
+
<Text className="body3 font-bold md:body1" data-testid="cart-total">
|
|
22
|
+
{cartTotalText}
|
|
23
|
+
</Text>
|
|
24
|
+
</div>
|
|
25
|
+
<MaterialIcon
|
|
26
|
+
name="keyboard_arrow_up"
|
|
27
|
+
size={24}
|
|
28
|
+
fill={0}
|
|
29
|
+
className={cx(
|
|
30
|
+
"h-6 w-6 transition-transform",
|
|
31
|
+
isOpen ? "rotate-180" : "rotate-0"
|
|
32
|
+
)}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
</Button>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type { ViewCartButtonProps } from "./types";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A custom hook that locks the body scroll based on the `isOpen` and `hideScrollOnIsOpenFalse` flags.
|
|
5
|
+
*
|
|
6
|
+
* @param {boolean} isOpen - Determines if the scroll should be locked (hidden).
|
|
7
|
+
* @param {boolean} hideScrollOnIsOpenFalse - Determines if the scroll should be reset when `isOpen` is false.
|
|
8
|
+
*/
|
|
9
|
+
function useBodyScrollLock(isOpen: boolean, hideScrollOnIsOpenFalse: boolean) {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (typeof document === "undefined") return;
|
|
12
|
+
|
|
13
|
+
// To avoid content shifting when the scrollbar appears/disappears
|
|
14
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
|
|
15
|
+
document.body.style.marginRight = isOpen ? `${scrollbarWidth}px` : "unset";
|
|
16
|
+
|
|
17
|
+
// Set the body overflow style based on the `isOpen` and `hideScrollOnIsOpenFalse` values.
|
|
18
|
+
document.body.style.overflow = isOpen
|
|
19
|
+
? "hidden"
|
|
20
|
+
: hideScrollOnIsOpenFalse
|
|
21
|
+
? "unset"
|
|
22
|
+
: document.body.style.overflow;
|
|
23
|
+
|
|
24
|
+
// Cleanup function to reset body overflow when the component unmounts or dependencies change.
|
|
25
|
+
return () => {
|
|
26
|
+
document.body.style.overflow = "unset";
|
|
27
|
+
};
|
|
28
|
+
}, [isOpen, hideScrollOnIsOpenFalse]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default useBodyScrollLock;
|