@windstream/react-shared-components 0.1.94 → 0.1.95
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/README.md +635 -635
- package/dist/contentful/index.esm.js +3 -3
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +3 -3
- package/dist/contentful/index.js.map +1 -1
- package/dist/core.d.ts +4 -4
- package/dist/index.esm.js +5 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +5 -13
- package/dist/index.js.map +1 -1
- package/dist/next/index.esm.js +2 -2
- package/dist/next/index.esm.js.map +1 -1
- package/dist/next/index.js +2 -2
- package/dist/next/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/index.esm.js +1 -1
- package/dist/utils/index.esm.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +191 -191
- package/src/components/accordion/Accordion.stories.tsx +230 -230
- package/src/components/accordion/index.test.tsx +270 -270
- package/src/components/accordion/index.tsx +70 -70
- package/src/components/accordion/types.ts +12 -12
- package/src/components/alert-card/AlertCard.stories.tsx +171 -171
- package/src/components/alert-card/index.test.tsx +152 -152
- package/src/components/alert-card/index.tsx +41 -41
- package/src/components/alert-card/types.ts +13 -13
- package/src/components/animation-wrapper/index.test.tsx +424 -424
- package/src/components/animation-wrapper/index.tsx +129 -129
- package/src/components/animation-wrapper/types.ts +11 -11
- package/src/components/brand-button/BrandButton.stories.tsx +223 -223
- package/src/components/brand-button/helpers.ts +35 -35
- package/src/components/brand-button/index.test.tsx +292 -292
- package/src/components/brand-button/index.tsx +120 -120
- package/src/components/brand-button/types.ts +38 -38
- package/src/components/button/Button.stories.tsx +108 -108
- package/src/components/button/index.test.tsx +91 -91
- package/src/components/button/index.tsx +27 -27
- package/src/components/button/types.ts +14 -14
- package/src/components/call-button/CallButton.stories.tsx +324 -324
- package/src/components/call-button/index.test.tsx +260 -260
- package/src/components/call-button/index.tsx +106 -106
- package/src/components/call-button/types.ts +16 -16
- package/src/components/checkbox/Checkbox.stories.tsx +247 -247
- package/src/components/checkbox/index.test.tsx +252 -252
- package/src/components/checkbox/index.tsx +197 -197
- package/src/components/checkbox/types.ts +27 -27
- package/src/components/checklist/Checklist.stories.tsx +150 -150
- package/src/components/checklist/index.test.tsx +231 -231
- package/src/components/checklist/index.tsx +96 -96
- package/src/components/checklist/types.ts +23 -23
- package/src/components/collapse/Collapse.stories.tsx +255 -255
- package/src/components/collapse/index.test.tsx +277 -277
- package/src/components/collapse/index.tsx +47 -47
- package/src/components/collapse/types.ts +6 -6
- package/src/components/divider/Divider.stories.tsx +205 -205
- package/src/components/divider/index.test.tsx +53 -53
- package/src/components/divider/index.tsx +22 -22
- package/src/components/divider/type.ts +3 -3
- package/src/components/image/Image.stories.tsx +113 -113
- package/src/components/image/index.test.tsx +174 -174
- package/src/components/image/index.tsx +25 -25
- package/src/components/image/types.ts +40 -40
- package/src/components/input/Input.stories.tsx +325 -325
- package/src/components/input/index.test.tsx +348 -348
- package/src/components/input/index.tsx +177 -177
- package/src/components/input/types.ts +37 -37
- package/src/components/link/Link.stories.tsx +163 -163
- package/src/components/link/index.test.tsx +199 -199
- package/src/components/link/index.tsx +116 -116
- package/src/components/link/types.ts +25 -25
- package/src/components/list/List.stories.tsx +272 -272
- package/src/components/list/index.test.tsx +166 -166
- package/src/components/list/index.tsx +88 -88
- package/src/components/list/list-item/index.tsx +38 -38
- package/src/components/list/list-item/types.ts +13 -13
- package/src/components/list/types.ts +29 -29
- package/src/components/material-icon/MaterialIcon.stories.tsx +322 -322
- package/src/components/material-icon/constants.ts +99 -99
- package/src/components/material-icon/index.test.tsx +130 -130
- package/src/components/material-icon/index.tsx +47 -47
- package/src/components/material-icon/types.ts +31 -31
- package/src/components/modal/Modal.stories.tsx +171 -171
- package/src/components/modal/index.test.tsx +310 -310
- package/src/components/modal/index.tsx +164 -164
- package/src/components/modal/types.ts +24 -24
- package/src/components/next-image/index.test.tsx +406 -406
- package/src/components/next-image/index.tsx +74 -74
- package/src/components/next-image/types.ts +1 -1
- package/src/components/pagination/index.test.tsx +521 -521
- package/src/components/pagination/index.tsx +91 -91
- package/src/components/pagination/types.ts +6 -6
- package/src/components/radio-button/RadioButton.stories.tsx +307 -307
- package/src/components/radio-button/index.test.tsx +151 -151
- package/src/components/radio-button/index.tsx +75 -75
- package/src/components/radio-button/types.ts +21 -21
- package/src/components/see-more/SeeMore.stories.tsx +181 -181
- package/src/components/see-more/index.test.tsx +96 -96
- package/src/components/see-more/index.tsx +44 -44
- package/src/components/see-more/types.ts +4 -4
- package/src/components/select/Select.stories.tsx +411 -411
- package/src/components/select/index.test.tsx +256 -256
- package/src/components/select/index.tsx +155 -155
- package/src/components/select/types.ts +36 -36
- package/src/components/select-plan-button/SelectPlanButton.stories.tsx +184 -184
- package/src/components/select-plan-button/index.test.tsx +173 -173
- package/src/components/select-plan-button/index.tsx +63 -63
- package/src/components/select-plan-button/types.ts +17 -17
- package/src/components/skeleton/Skeleton.stories.tsx +179 -179
- package/src/components/skeleton/index.test.tsx +74 -74
- package/src/components/skeleton/index.tsx +61 -61
- package/src/components/skeleton/types.ts +4 -4
- package/src/components/spinner/Spinner.stories.tsx +335 -335
- package/src/components/spinner/index.test.tsx +76 -76
- package/src/components/spinner/index.tsx +44 -44
- package/src/components/spinner/types.ts +5 -5
- package/src/components/text/Text.stories.tsx +321 -321
- package/src/components/text/index.test.tsx +65 -65
- package/src/components/text/index.tsx +25 -25
- package/src/components/text/types.ts +45 -45
- package/src/components/tooltip/Tooltip.stories.tsx +219 -219
- package/src/components/tooltip/index.test.tsx +50 -50
- package/src/components/tooltip/index.tsx +74 -74
- package/src/components/tooltip/types.ts +7 -7
- package/src/components/view-cart-button/ViewCartButton.stories.tsx +252 -252
- package/src/components/view-cart-button/index.test.tsx +57 -57
- package/src/components/view-cart-button/index.tsx +42 -42
- package/src/components/view-cart-button/types.ts +5 -5
- package/src/contentful/blocks/accordion/Accordion.stories.mocks.tsx +128 -128
- package/src/contentful/blocks/accordion/Accordion.stories.tsx +98 -98
- package/src/contentful/blocks/accordion/index.test.tsx +218 -218
- package/src/contentful/blocks/accordion/index.tsx +114 -114
- package/src/contentful/blocks/accordion/types.ts +34 -34
- package/src/contentful/blocks/address-input-banner/index.test.tsx +132 -132
- package/src/contentful/blocks/address-input-banner/index.tsx +52 -52
- package/src/contentful/blocks/address-input-banner/types.ts +14 -14
- package/src/contentful/blocks/anchored-bottom-banner/index.test.tsx +287 -287
- package/src/contentful/blocks/anchored-bottom-banner/index.tsx +181 -181
- package/src/contentful/blocks/anchored-bottom-banner/types.ts +13 -13
- package/src/contentful/blocks/blogs-grid/BlogGrid.stories.mocks.tsx +144 -144
- package/src/contentful/blocks/blogs-grid/BlogGrid.stories.tsx +157 -157
- package/src/contentful/blocks/blogs-grid/index.test.tsx +355 -355
- package/src/contentful/blocks/blogs-grid/index.tsx +134 -134
- package/src/contentful/blocks/blogs-grid/types.ts +26 -26
- package/src/contentful/blocks/blogs-grid-base/index.test.tsx +274 -274
- package/src/contentful/blocks/blogs-grid-base/index.tsx +119 -119
- package/src/contentful/blocks/blogs-grid-base/types.ts +36 -36
- package/src/contentful/blocks/breadcrumbs/BreadcrumbNavigation.stories.tsx +147 -147
- package/src/contentful/blocks/breadcrumbs/index.test.tsx +281 -281
- package/src/contentful/blocks/breadcrumbs/index.tsx +95 -95
- package/src/contentful/blocks/breadcrumbs/types.ts +8 -8
- package/src/contentful/blocks/button/Button.stories.tsx +40 -40
- package/src/contentful/blocks/button/index.test.tsx +339 -339
- package/src/contentful/blocks/button/index.tsx +131 -131
- package/src/contentful/blocks/button/types.ts +39 -39
- package/src/contentful/blocks/callout/Callout.stories.tsx +23 -23
- package/src/contentful/blocks/callout/index.test.tsx +539 -539
- package/src/contentful/blocks/callout/index.tsx +277 -277
- package/src/contentful/blocks/callout/types.ts +78 -78
- package/src/contentful/blocks/cards/Cards.stories.tsx +23 -23
- package/src/contentful/blocks/cards/blog-card/index.test.tsx +218 -218
- package/src/contentful/blocks/cards/blog-card/index.tsx +129 -129
- package/src/contentful/blocks/cards/blog-card/types.ts +34 -34
- package/src/contentful/blocks/cards/floating-image-card/index.test.tsx +201 -201
- package/src/contentful/blocks/cards/floating-image-card/index.tsx +119 -119
- package/src/contentful/blocks/cards/floating-image-card/types.ts +30 -30
- package/src/contentful/blocks/cards/full-image-card/index.test.tsx +216 -216
- package/src/contentful/blocks/cards/full-image-card/index.tsx +130 -130
- package/src/contentful/blocks/cards/full-image-card/types.ts +29 -29
- package/src/contentful/blocks/cards/index.test.tsx +39 -39
- package/src/contentful/blocks/cards/index.tsx +13 -13
- package/src/contentful/blocks/cards/product-card/index.test.tsx +263 -263
- package/src/contentful/blocks/cards/product-card/index.tsx +251 -251
- package/src/contentful/blocks/cards/product-card/types.ts +28 -28
- package/src/contentful/blocks/cards/simple-card/index.test.tsx +364 -364
- package/src/contentful/blocks/cards/simple-card/index.tsx +325 -325
- package/src/contentful/blocks/cards/simple-card/types.ts +71 -71
- package/src/contentful/blocks/cards/testimonial-card/index.test.tsx +180 -180
- package/src/contentful/blocks/cards/testimonial-card/index.tsx +90 -90
- package/src/contentful/blocks/cards/testimonial-card/types.tsx +12 -12
- package/src/contentful/blocks/cards/types.ts +1 -1
- package/src/contentful/blocks/carousel/Carousel.stories.tsx +23 -23
- package/src/contentful/blocks/carousel/helper.test.tsx +539 -539
- package/src/contentful/blocks/carousel/helper.tsx +494 -494
- package/src/contentful/blocks/carousel/index.test.tsx +308 -308
- package/src/contentful/blocks/carousel/index.tsx +87 -87
- package/src/contentful/blocks/carousel/types.test.ts +16 -16
- package/src/contentful/blocks/carousel/types.ts +145 -145
- package/src/contentful/blocks/cart-retention-banner/index.test.tsx +409 -409
- package/src/contentful/blocks/cart-retention-banner/index.tsx +109 -109
- package/src/contentful/blocks/cart-retention-banner/types.ts +11 -11
- package/src/contentful/blocks/comparison-table/index.test.tsx +114 -114
- package/src/contentful/blocks/comparison-table/index.tsx +29 -29
- package/src/contentful/blocks/comparison-table/types.ts +6 -6
- package/src/contentful/blocks/cookiebanner/index.test.tsx +277 -277
- package/src/contentful/blocks/cookiebanner/index.tsx +146 -146
- package/src/contentful/blocks/cookiebanner/type.ts +7 -7
- package/src/contentful/blocks/cta-callout/CtaCallout.stories.tsx +46 -46
- package/src/contentful/blocks/cta-callout/index.test.tsx +244 -244
- package/src/contentful/blocks/cta-callout/index.tsx +73 -73
- package/src/contentful/blocks/cta-callout/types.ts +26 -26
- package/src/contentful/blocks/dynamic-tabs/index.test.tsx +240 -240
- package/src/contentful/blocks/dynamic-tabs/index.tsx +204 -204
- package/src/contentful/blocks/dynamic-tabs/types.ts +21 -21
- package/src/contentful/blocks/email-input-block/index.test.tsx +213 -213
- package/src/contentful/blocks/email-input-block/index.tsx +121 -121
- package/src/contentful/blocks/email-input-block/types.ts +16 -16
- package/src/contentful/blocks/find-kinetic/FindKinetic.stories.tsx +23 -23
- package/src/contentful/blocks/find-kinetic/index.test.tsx +269 -269
- package/src/contentful/blocks/find-kinetic/index.tsx +138 -138
- package/src/contentful/blocks/find-kinetic/types.ts +20 -20
- package/src/contentful/blocks/floating-banner/FloatingBanner.stories.tsx +34 -34
- package/src/contentful/blocks/floating-banner/index.test.tsx +246 -246
- package/src/contentful/blocks/floating-banner/index.tsx +97 -97
- package/src/contentful/blocks/floating-banner/types.ts +22 -22
- package/src/contentful/blocks/footer/Footer.stories.tsx +317 -317
- package/src/contentful/blocks/footer/index.test.tsx +302 -302
- package/src/contentful/blocks/footer/index.tsx +91 -91
- package/src/contentful/blocks/footer/types.ts +13 -13
- package/src/contentful/blocks/image-promo-bar/ImagePromoBar.stories.tsx +23 -23
- package/src/contentful/blocks/image-promo-bar/helper.test.tsx +61 -61
- package/src/contentful/blocks/image-promo-bar/helper.tsx +28 -28
- package/src/contentful/blocks/image-promo-bar/index.test.tsx +467 -467
- package/src/contentful/blocks/image-promo-bar/index.tsx +8 -6
- package/src/contentful/blocks/image-promo-bar/types.ts +44 -44
- package/src/contentful/blocks/image-promo-bar/vimeo-embed.test.tsx +142 -142
- package/src/contentful/blocks/image-promo-bar/vimeo-embed.tsx +93 -93
- package/src/contentful/blocks/image-promo-bar/youtube-embed.test.tsx +104 -104
- package/src/contentful/blocks/image-promo-bar/youtube-embed.tsx +46 -46
- package/src/contentful/blocks/modal/constants.ts +53 -53
- package/src/contentful/blocks/modal/index.test.tsx +209 -209
- package/src/contentful/blocks/modal/index.tsx +108 -108
- package/src/contentful/blocks/modal/types.ts +12 -12
- package/src/contentful/blocks/navigation/Navigation.stories.mocks.tsx +78 -78
- package/src/contentful/blocks/navigation/Navigation.stories.tsx +138 -138
- package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.test.tsx +208 -208
- package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.tsx +141 -141
- package/src/contentful/blocks/navigation/index.test.tsx +924 -924
- package/src/contentful/blocks/navigation/index.tsx +569 -569
- package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.test.tsx +131 -131
- package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.tsx +82 -82
- package/src/contentful/blocks/navigation/types.ts +71 -71
- package/src/contentful/blocks/primary-hero/PrimaryHero.stories.tsx +23 -23
- package/src/contentful/blocks/primary-hero/index.test.tsx +286 -286
- package/src/contentful/blocks/primary-hero/index.tsx +239 -239
- package/src/contentful/blocks/primary-hero/types.ts +37 -37
- package/src/contentful/blocks/search-block/index.test.tsx +268 -268
- package/src/contentful/blocks/search-block/index.tsx +90 -90
- package/src/contentful/blocks/search-block/types.ts +15 -15
- package/src/contentful/blocks/shape-background-wrapper/ShapeBackgroundWrapper.stories.tsx +26 -26
- package/src/contentful/blocks/shape-background-wrapper/index.test.tsx +284 -284
- package/src/contentful/blocks/shape-background-wrapper/index.tsx +124 -124
- package/src/contentful/blocks/shape-background-wrapper/types.ts +36 -36
- package/src/contentful/blocks/text/Text.stories.tsx +23 -23
- package/src/contentful/blocks/text/index.test.tsx +36 -36
- package/src/contentful/blocks/text/index.tsx +12 -12
- package/src/contentful/blocks/text/types.ts +1 -1
- package/src/contentful/index.test.ts +45 -45
- package/src/contentful/index.ts +105 -105
- package/src/global-mocks/contentful/to-document.ts +25 -25
- package/src/global-mocks/cookie.ts +48 -48
- package/src/global-mocks/cx.ts +37 -37
- package/src/global-mocks/index.ts +89 -89
- package/src/global-mocks/speed-card-bg.ts +27 -27
- package/src/global-mocks/utm.ts +49 -49
- package/src/hooks/contentful/use-contentful-rich-text.test.tsx +1758 -1758
- package/src/hooks/contentful/use-contentful-rich-text.tsx +309 -309
- package/src/hooks/contentful/use-processed-check-list.test.tsx +277 -277
- package/src/hooks/contentful/use-processed-check-list.ts +63 -63
- package/src/hooks/use-body-scroll-lock.test.ts +134 -134
- package/src/hooks/use-body-scroll-lock.ts +34 -34
- package/src/hooks/use-carousel-swipe.test.ts +393 -393
- package/src/hooks/use-carousel-swipe.ts +264 -264
- package/src/hooks/use-outside-click.test.ts +142 -142
- package/src/hooks/use-outside-click.ts +17 -17
- package/src/index.ts +107 -107
- package/src/next/index.test.ts +7 -7
- package/src/next/index.ts +5 -5
- package/src/setupTests.ts +52 -52
- package/src/stories/DocsTemplate.tsx +24 -24
- package/src/styles/globals.css +343 -343
- package/src/types/global.d.ts +9 -9
- package/src/types/micro-components.ts +99 -99
- package/src/types/utm.ts +49 -49
- package/src/utils/contentful/to-document.test.ts +85 -85
- package/src/utils/contentful/to-document.ts +24 -24
- package/src/utils/cookie.test.ts +180 -180
- package/src/utils/cookie.ts +84 -84
- package/src/utils/cx.test.ts +90 -90
- package/src/utils/cx.ts +49 -49
- package/src/utils/index.test.ts +115 -115
- package/src/utils/index.ts +41 -41
- package/src/utils/speed-card-bg.test.ts +46 -46
- package/src/utils/speed-card-bg.ts +24 -24
- package/src/utils/utm.test.ts +359 -359
- package/src/utils/utm.ts +221 -221
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Link } from "./index";
|
|
3
|
-
|
|
4
|
-
import { fireEvent, render, screen } from "@testing-library/react";
|
|
5
|
-
|
|
6
|
-
import "@testing-library/jest-dom";
|
|
7
|
-
|
|
8
|
-
// Mock next/link
|
|
9
|
-
jest.mock("next/link", () => {
|
|
10
|
-
const MockLink = ({ children, href, className, onClick, ...rest }: any) => (
|
|
11
|
-
<a href={href} className={className} onClick={onClick} {...rest}>
|
|
12
|
-
{children}
|
|
13
|
-
</a>
|
|
14
|
-
);
|
|
15
|
-
MockLink.displayName = "MockNextLink";
|
|
16
|
-
return MockLink;
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe("Link", () => {
|
|
20
|
-
describe("Rendering", () => {
|
|
21
|
-
it("renders children content", () => {
|
|
22
|
-
render(<Link href="/test">Click me</Link>);
|
|
23
|
-
expect(screen.getByText("Click me")).toBeInTheDocument();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("has correct displayName", () => {
|
|
27
|
-
expect(Link.displayName).toBe("Link");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("renders as a Next.js Link for internal links", () => {
|
|
31
|
-
render(<Link href="/internal">Internal</Link>);
|
|
32
|
-
const el = screen.getByText("Internal");
|
|
33
|
-
expect(el.closest("a")).toHaveAttribute("href", "/internal");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("renders as <a> for external links", () => {
|
|
37
|
-
render(
|
|
38
|
-
<Link href="https://example.com" external={true}>
|
|
39
|
-
External
|
|
40
|
-
</Link>
|
|
41
|
-
);
|
|
42
|
-
const el = screen.getByText("External");
|
|
43
|
-
expect(el.closest("a")).toHaveAttribute("href", "https://example.com");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("renders as <a> for http links even without external prop", () => {
|
|
47
|
-
render(<Link href="https://example.com">Auto External</Link>);
|
|
48
|
-
const el = screen.getByText("Auto External");
|
|
49
|
-
expect(el.closest("a")).toHaveAttribute("href", "https://example.com");
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe("Variants", () => {
|
|
54
|
-
it("applies unstyled variant with no extra classes", () => {
|
|
55
|
-
render(
|
|
56
|
-
<Link href="/test" variant="unstyled">
|
|
57
|
-
Unstyled
|
|
58
|
-
</Link>
|
|
59
|
-
);
|
|
60
|
-
const el = screen.getByText("Unstyled").closest("a");
|
|
61
|
-
expect(el?.className).toContain("link--unstyled");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("applies default variant classes", () => {
|
|
65
|
-
render(
|
|
66
|
-
<Link href="/test" variant="default">
|
|
67
|
-
Default
|
|
68
|
-
</Link>
|
|
69
|
-
);
|
|
70
|
-
const el = screen.getByText("Default").closest("a");
|
|
71
|
-
expect(el?.className).toContain("link--default");
|
|
72
|
-
expect(el?.className).toContain("underline");
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("Disabled state", () => {
|
|
77
|
-
it("adds disabled class when disabled", () => {
|
|
78
|
-
render(
|
|
79
|
-
<Link href="/test" disabled={true}>
|
|
80
|
-
Disabled
|
|
81
|
-
</Link>
|
|
82
|
-
);
|
|
83
|
-
const el = screen.getByText("Disabled").closest("a");
|
|
84
|
-
expect(el?.className).toContain("link--disabled");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("sets aria-disabled when disabled", () => {
|
|
88
|
-
render(
|
|
89
|
-
<Link href="/test" disabled={true} external={true}>
|
|
90
|
-
Disabled
|
|
91
|
-
</Link>
|
|
92
|
-
);
|
|
93
|
-
const el = screen.getByText("Disabled").closest("a");
|
|
94
|
-
expect(el).toHaveAttribute("aria-disabled", "true");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("sets tabIndex -1 when disabled", () => {
|
|
98
|
-
render(
|
|
99
|
-
<Link href="/test" disabled={true} external={true}>
|
|
100
|
-
Disabled
|
|
101
|
-
</Link>
|
|
102
|
-
);
|
|
103
|
-
const el = screen.getByText("Disabled").closest("a");
|
|
104
|
-
expect(el).toHaveAttribute("tabindex", "-1");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("prevents click when disabled", () => {
|
|
108
|
-
const onClick = jest.fn();
|
|
109
|
-
render(
|
|
110
|
-
<Link href="/test" disabled={true} external={true} onClick={onClick}>
|
|
111
|
-
Disabled
|
|
112
|
-
</Link>
|
|
113
|
-
);
|
|
114
|
-
const el = screen.getByText("Disabled").closest("a")!;
|
|
115
|
-
fireEvent.click(el);
|
|
116
|
-
expect(onClick).not.toHaveBeenCalled();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe("External links", () => {
|
|
121
|
-
it("adds target=_blank for external links", () => {
|
|
122
|
-
render(
|
|
123
|
-
<Link href="https://example.com" external={true}>
|
|
124
|
-
External
|
|
125
|
-
</Link>
|
|
126
|
-
);
|
|
127
|
-
const el = screen.getByText("External").closest("a");
|
|
128
|
-
expect(el).toHaveAttribute("target", "_blank");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("adds rel=noopener noreferrer for external links", () => {
|
|
132
|
-
render(
|
|
133
|
-
<Link href="https://example.com" external={true}>
|
|
134
|
-
External
|
|
135
|
-
</Link>
|
|
136
|
-
);
|
|
137
|
-
const el = screen.getByText("External").closest("a");
|
|
138
|
-
expect(el).toHaveAttribute("rel", "noopener noreferrer");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("does not add target for disabled external links", () => {
|
|
142
|
-
render(
|
|
143
|
-
<Link href="https://example.com" external={true} disabled={true}>
|
|
144
|
-
External
|
|
145
|
-
</Link>
|
|
146
|
-
);
|
|
147
|
-
const el = screen.getByText("External").closest("a");
|
|
148
|
-
expect(el).not.toHaveAttribute("target", "_blank");
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe("Event handling", () => {
|
|
153
|
-
it("calls onClick handler when clicked", () => {
|
|
154
|
-
const onClick = jest.fn();
|
|
155
|
-
render(
|
|
156
|
-
<Link href="https://example.com" external={true} onClick={onClick}>
|
|
157
|
-
Click
|
|
158
|
-
</Link>
|
|
159
|
-
);
|
|
160
|
-
const el = screen.getByText("Click").closest("a")!;
|
|
161
|
-
fireEvent.click(el);
|
|
162
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe("Ref forwarding", () => {
|
|
167
|
-
it("forwards ref to the anchor element", () => {
|
|
168
|
-
const ref = React.createRef<HTMLAnchorElement>();
|
|
169
|
-
render(
|
|
170
|
-
<Link ref={ref} href="https://test.com" external={true}>
|
|
171
|
-
Ref
|
|
172
|
-
</Link>
|
|
173
|
-
);
|
|
174
|
-
expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe("Custom className and style", () => {
|
|
179
|
-
it("applies custom className", () => {
|
|
180
|
-
render(
|
|
181
|
-
<Link href="/test" className="custom-class">
|
|
182
|
-
Custom
|
|
183
|
-
</Link>
|
|
184
|
-
);
|
|
185
|
-
const el = screen.getByText("Custom").closest("a");
|
|
186
|
-
expect(el?.className).toContain("custom-class");
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("applies inline style", () => {
|
|
190
|
-
render(
|
|
191
|
-
<Link href="https://test.com" external={true} style={{ color: "red" }}>
|
|
192
|
-
Styled
|
|
193
|
-
</Link>
|
|
194
|
-
);
|
|
195
|
-
const el = screen.getByText("Styled").closest("a")!;
|
|
196
|
-
expect(el.style.color).toBe("red");
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
});
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Link } from "./index";
|
|
3
|
+
|
|
4
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
5
|
+
|
|
6
|
+
import "@testing-library/jest-dom";
|
|
7
|
+
|
|
8
|
+
// Mock next/link
|
|
9
|
+
jest.mock("next/link", () => {
|
|
10
|
+
const MockLink = ({ children, href, className, onClick, ...rest }: any) => (
|
|
11
|
+
<a href={href} className={className} onClick={onClick} {...rest}>
|
|
12
|
+
{children}
|
|
13
|
+
</a>
|
|
14
|
+
);
|
|
15
|
+
MockLink.displayName = "MockNextLink";
|
|
16
|
+
return MockLink;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("Link", () => {
|
|
20
|
+
describe("Rendering", () => {
|
|
21
|
+
it("renders children content", () => {
|
|
22
|
+
render(<Link href="/test">Click me</Link>);
|
|
23
|
+
expect(screen.getByText("Click me")).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("has correct displayName", () => {
|
|
27
|
+
expect(Link.displayName).toBe("Link");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("renders as a Next.js Link for internal links", () => {
|
|
31
|
+
render(<Link href="/internal">Internal</Link>);
|
|
32
|
+
const el = screen.getByText("Internal");
|
|
33
|
+
expect(el.closest("a")).toHaveAttribute("href", "/internal");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("renders as <a> for external links", () => {
|
|
37
|
+
render(
|
|
38
|
+
<Link href="https://example.com" external={true}>
|
|
39
|
+
External
|
|
40
|
+
</Link>
|
|
41
|
+
);
|
|
42
|
+
const el = screen.getByText("External");
|
|
43
|
+
expect(el.closest("a")).toHaveAttribute("href", "https://example.com");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("renders as <a> for http links even without external prop", () => {
|
|
47
|
+
render(<Link href="https://example.com">Auto External</Link>);
|
|
48
|
+
const el = screen.getByText("Auto External");
|
|
49
|
+
expect(el.closest("a")).toHaveAttribute("href", "https://example.com");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("Variants", () => {
|
|
54
|
+
it("applies unstyled variant with no extra classes", () => {
|
|
55
|
+
render(
|
|
56
|
+
<Link href="/test" variant="unstyled">
|
|
57
|
+
Unstyled
|
|
58
|
+
</Link>
|
|
59
|
+
);
|
|
60
|
+
const el = screen.getByText("Unstyled").closest("a");
|
|
61
|
+
expect(el?.className).toContain("link--unstyled");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("applies default variant classes", () => {
|
|
65
|
+
render(
|
|
66
|
+
<Link href="/test" variant="default">
|
|
67
|
+
Default
|
|
68
|
+
</Link>
|
|
69
|
+
);
|
|
70
|
+
const el = screen.getByText("Default").closest("a");
|
|
71
|
+
expect(el?.className).toContain("link--default");
|
|
72
|
+
expect(el?.className).toContain("underline");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("Disabled state", () => {
|
|
77
|
+
it("adds disabled class when disabled", () => {
|
|
78
|
+
render(
|
|
79
|
+
<Link href="/test" disabled={true}>
|
|
80
|
+
Disabled
|
|
81
|
+
</Link>
|
|
82
|
+
);
|
|
83
|
+
const el = screen.getByText("Disabled").closest("a");
|
|
84
|
+
expect(el?.className).toContain("link--disabled");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("sets aria-disabled when disabled", () => {
|
|
88
|
+
render(
|
|
89
|
+
<Link href="/test" disabled={true} external={true}>
|
|
90
|
+
Disabled
|
|
91
|
+
</Link>
|
|
92
|
+
);
|
|
93
|
+
const el = screen.getByText("Disabled").closest("a");
|
|
94
|
+
expect(el).toHaveAttribute("aria-disabled", "true");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("sets tabIndex -1 when disabled", () => {
|
|
98
|
+
render(
|
|
99
|
+
<Link href="/test" disabled={true} external={true}>
|
|
100
|
+
Disabled
|
|
101
|
+
</Link>
|
|
102
|
+
);
|
|
103
|
+
const el = screen.getByText("Disabled").closest("a");
|
|
104
|
+
expect(el).toHaveAttribute("tabindex", "-1");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("prevents click when disabled", () => {
|
|
108
|
+
const onClick = jest.fn();
|
|
109
|
+
render(
|
|
110
|
+
<Link href="/test" disabled={true} external={true} onClick={onClick}>
|
|
111
|
+
Disabled
|
|
112
|
+
</Link>
|
|
113
|
+
);
|
|
114
|
+
const el = screen.getByText("Disabled").closest("a")!;
|
|
115
|
+
fireEvent.click(el);
|
|
116
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("External links", () => {
|
|
121
|
+
it("adds target=_blank for external links", () => {
|
|
122
|
+
render(
|
|
123
|
+
<Link href="https://example.com" external={true}>
|
|
124
|
+
External
|
|
125
|
+
</Link>
|
|
126
|
+
);
|
|
127
|
+
const el = screen.getByText("External").closest("a");
|
|
128
|
+
expect(el).toHaveAttribute("target", "_blank");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("adds rel=noopener noreferrer for external links", () => {
|
|
132
|
+
render(
|
|
133
|
+
<Link href="https://example.com" external={true}>
|
|
134
|
+
External
|
|
135
|
+
</Link>
|
|
136
|
+
);
|
|
137
|
+
const el = screen.getByText("External").closest("a");
|
|
138
|
+
expect(el).toHaveAttribute("rel", "noopener noreferrer");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("does not add target for disabled external links", () => {
|
|
142
|
+
render(
|
|
143
|
+
<Link href="https://example.com" external={true} disabled={true}>
|
|
144
|
+
External
|
|
145
|
+
</Link>
|
|
146
|
+
);
|
|
147
|
+
const el = screen.getByText("External").closest("a");
|
|
148
|
+
expect(el).not.toHaveAttribute("target", "_blank");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("Event handling", () => {
|
|
153
|
+
it("calls onClick handler when clicked", () => {
|
|
154
|
+
const onClick = jest.fn();
|
|
155
|
+
render(
|
|
156
|
+
<Link href="https://example.com" external={true} onClick={onClick}>
|
|
157
|
+
Click
|
|
158
|
+
</Link>
|
|
159
|
+
);
|
|
160
|
+
const el = screen.getByText("Click").closest("a")!;
|
|
161
|
+
fireEvent.click(el);
|
|
162
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("Ref forwarding", () => {
|
|
167
|
+
it("forwards ref to the anchor element", () => {
|
|
168
|
+
const ref = React.createRef<HTMLAnchorElement>();
|
|
169
|
+
render(
|
|
170
|
+
<Link ref={ref} href="https://test.com" external={true}>
|
|
171
|
+
Ref
|
|
172
|
+
</Link>
|
|
173
|
+
);
|
|
174
|
+
expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("Custom className and style", () => {
|
|
179
|
+
it("applies custom className", () => {
|
|
180
|
+
render(
|
|
181
|
+
<Link href="/test" className="custom-class">
|
|
182
|
+
Custom
|
|
183
|
+
</Link>
|
|
184
|
+
);
|
|
185
|
+
const el = screen.getByText("Custom").closest("a");
|
|
186
|
+
expect(el?.className).toContain("custom-class");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("applies inline style", () => {
|
|
190
|
+
render(
|
|
191
|
+
<Link href="https://test.com" external={true} style={{ color: "red" }}>
|
|
192
|
+
Styled
|
|
193
|
+
</Link>
|
|
194
|
+
);
|
|
195
|
+
const el = screen.getByText("Styled").closest("a")!;
|
|
196
|
+
expect(el.style.color).toBe("red");
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { forwardRef } from "react";
|
|
4
|
-
import { cx } from "../../utils/cx";
|
|
5
|
-
import { LinkProps } from "./types";
|
|
6
|
-
import NextLinkImport from "next/link";
|
|
7
|
-
|
|
8
|
-
// Handle CJS/ESM interop: when bundled as ESM and consumed by Webpack,
|
|
9
|
-
// the default import may resolve to { default: Component } instead of the function.
|
|
10
|
-
const NextLink =
|
|
11
|
-
typeof NextLinkImport === "function"
|
|
12
|
-
? NextLinkImport
|
|
13
|
-
: ((NextLinkImport as any).default ?? NextLinkImport);
|
|
14
|
-
|
|
15
|
-
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
|
|
16
|
-
(
|
|
17
|
-
{
|
|
18
|
-
children,
|
|
19
|
-
href,
|
|
20
|
-
className = "",
|
|
21
|
-
onClick,
|
|
22
|
-
variant = "unstyled",
|
|
23
|
-
style,
|
|
24
|
-
external = false,
|
|
25
|
-
disabled = false,
|
|
26
|
-
...props
|
|
27
|
-
},
|
|
28
|
-
ref
|
|
29
|
-
) => {
|
|
30
|
-
// Get Tailwind classes for different variants
|
|
31
|
-
const getVariantClasses = () => {
|
|
32
|
-
if (variant === "unstyled") return "";
|
|
33
|
-
|
|
34
|
-
// TODO: Add styles based on the figma design for all variants
|
|
35
|
-
const baseClasses =
|
|
36
|
-
"transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2";
|
|
37
|
-
|
|
38
|
-
const variantClasses = {
|
|
39
|
-
default: "text-text underline",
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const stateClasses = [
|
|
43
|
-
disabled
|
|
44
|
-
? "opacity-60 cursor-not-allowed pointer-events-none"
|
|
45
|
-
: "cursor-pointer",
|
|
46
|
-
]
|
|
47
|
-
.filter(Boolean)
|
|
48
|
-
.join(" ");
|
|
49
|
-
|
|
50
|
-
return [
|
|
51
|
-
baseClasses,
|
|
52
|
-
variantClasses[variant as keyof typeof variantClasses] ||
|
|
53
|
-
variantClasses.default,
|
|
54
|
-
stateClasses,
|
|
55
|
-
]
|
|
56
|
-
.filter(Boolean)
|
|
57
|
-
.join(" ");
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const tailwindClasses = getVariantClasses();
|
|
61
|
-
|
|
62
|
-
// Handle click events
|
|
63
|
-
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
64
|
-
if (disabled) {
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
onClick?.(event);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Combine all classes
|
|
73
|
-
const combinedClassName = cx(
|
|
74
|
-
tailwindClasses,
|
|
75
|
-
`link--${variant}`,
|
|
76
|
-
disabled && "link--disabled",
|
|
77
|
-
className
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
// Determine link props based on external/internal
|
|
81
|
-
const linkProps = {
|
|
82
|
-
...props,
|
|
83
|
-
ref,
|
|
84
|
-
className: combinedClassName,
|
|
85
|
-
style,
|
|
86
|
-
href: disabled ? undefined : href,
|
|
87
|
-
onClick: handleClick,
|
|
88
|
-
...(external &&
|
|
89
|
-
!disabled && {
|
|
90
|
-
target: "_blank",
|
|
91
|
-
rel: "noopener noreferrer",
|
|
92
|
-
}),
|
|
93
|
-
...(disabled && {
|
|
94
|
-
"aria-disabled": true,
|
|
95
|
-
tabIndex: -1,
|
|
96
|
-
}),
|
|
97
|
-
};
|
|
98
|
-
if (external || (typeof href === "string" && href.startsWith("http"))) {
|
|
99
|
-
return <a {...linkProps}>{children}</a>;
|
|
100
|
-
}
|
|
101
|
-
return (
|
|
102
|
-
<NextLink
|
|
103
|
-
href={href || "#"}
|
|
104
|
-
className={combinedClassName}
|
|
105
|
-
onClick={handleClick}
|
|
106
|
-
{...props}
|
|
107
|
-
>
|
|
108
|
-
{children}
|
|
109
|
-
</NextLink>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
Link.displayName = "Link";
|
|
115
|
-
|
|
116
|
-
export type { LinkProps };
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef } from "react";
|
|
4
|
+
import { cx } from "../../utils/cx";
|
|
5
|
+
import { LinkProps } from "./types";
|
|
6
|
+
import NextLinkImport from "next/link";
|
|
7
|
+
|
|
8
|
+
// Handle CJS/ESM interop: when bundled as ESM and consumed by Webpack,
|
|
9
|
+
// the default import may resolve to { default: Component } instead of the function.
|
|
10
|
+
const NextLink =
|
|
11
|
+
typeof NextLinkImport === "function"
|
|
12
|
+
? NextLinkImport
|
|
13
|
+
: ((NextLinkImport as any).default ?? NextLinkImport);
|
|
14
|
+
|
|
15
|
+
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
|
|
16
|
+
(
|
|
17
|
+
{
|
|
18
|
+
children,
|
|
19
|
+
href,
|
|
20
|
+
className = "",
|
|
21
|
+
onClick,
|
|
22
|
+
variant = "unstyled",
|
|
23
|
+
style,
|
|
24
|
+
external = false,
|
|
25
|
+
disabled = false,
|
|
26
|
+
...props
|
|
27
|
+
},
|
|
28
|
+
ref
|
|
29
|
+
) => {
|
|
30
|
+
// Get Tailwind classes for different variants
|
|
31
|
+
const getVariantClasses = () => {
|
|
32
|
+
if (variant === "unstyled") return "";
|
|
33
|
+
|
|
34
|
+
// TODO: Add styles based on the figma design for all variants
|
|
35
|
+
const baseClasses =
|
|
36
|
+
"transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2";
|
|
37
|
+
|
|
38
|
+
const variantClasses = {
|
|
39
|
+
default: "text-text underline",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const stateClasses = [
|
|
43
|
+
disabled
|
|
44
|
+
? "opacity-60 cursor-not-allowed pointer-events-none"
|
|
45
|
+
: "cursor-pointer",
|
|
46
|
+
]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join(" ");
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
baseClasses,
|
|
52
|
+
variantClasses[variant as keyof typeof variantClasses] ||
|
|
53
|
+
variantClasses.default,
|
|
54
|
+
stateClasses,
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(" ");
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const tailwindClasses = getVariantClasses();
|
|
61
|
+
|
|
62
|
+
// Handle click events
|
|
63
|
+
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
64
|
+
if (disabled) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onClick?.(event);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Combine all classes
|
|
73
|
+
const combinedClassName = cx(
|
|
74
|
+
tailwindClasses,
|
|
75
|
+
`link--${variant}`,
|
|
76
|
+
disabled && "link--disabled",
|
|
77
|
+
className
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Determine link props based on external/internal
|
|
81
|
+
const linkProps = {
|
|
82
|
+
...props,
|
|
83
|
+
ref,
|
|
84
|
+
className: combinedClassName,
|
|
85
|
+
style,
|
|
86
|
+
href: disabled ? undefined : href,
|
|
87
|
+
onClick: handleClick,
|
|
88
|
+
...(external &&
|
|
89
|
+
!disabled && {
|
|
90
|
+
target: "_blank",
|
|
91
|
+
rel: "noopener noreferrer",
|
|
92
|
+
}),
|
|
93
|
+
...(disabled && {
|
|
94
|
+
"aria-disabled": true,
|
|
95
|
+
tabIndex: -1,
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
if (external || (typeof href === "string" && href.startsWith("http"))) {
|
|
99
|
+
return <a {...linkProps}>{children}</a>;
|
|
100
|
+
}
|
|
101
|
+
return (
|
|
102
|
+
<NextLink
|
|
103
|
+
href={href || "#"}
|
|
104
|
+
className={combinedClassName}
|
|
105
|
+
onClick={handleClick}
|
|
106
|
+
{...props}
|
|
107
|
+
>
|
|
108
|
+
{children}
|
|
109
|
+
</NextLink>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
Link.displayName = "Link";
|
|
115
|
+
|
|
116
|
+
export type { LinkProps };
|