@times-components/ts-components 1.113.1 → 1.115.0

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.
@@ -0,0 +1,52 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import { Container, Disclaimer, TextContainer } from './styles';
3
+
4
+ interface AttributesProps {
5
+ disclaimer_text: string;
6
+ toggle_active_text: string;
7
+ toggle_inactive_text: string;
8
+ disclaimer_full_text: string;
9
+ }
10
+
11
+ interface RootProps {
12
+ attributes: AttributesProps;
13
+ }
14
+
15
+ export const AffiliateLinkDisclaimer: React.FC<RootProps> = props => {
16
+ const attributes = props.attributes;
17
+
18
+ const [open, setOpen] = useState<boolean>(false);
19
+ const ref = useRef<HTMLParagraphElement>(null);
20
+
21
+ const getHeight = (): string => {
22
+ return ref.current
23
+ ? `${ref.current.getBoundingClientRect().height}px`
24
+ : '0px';
25
+ };
26
+
27
+ return (
28
+ <Container>
29
+ <Disclaimer>
30
+ <p className="shortcode-disclaimer_text">
31
+ {decodeURIComponent(attributes.disclaimer_text)}
32
+ </p>
33
+ <a
34
+ className="shortcode-disclaimer__toggle"
35
+ href="#"
36
+ onClick={e => {
37
+ e.preventDefault();
38
+ setOpen(!open);
39
+ }}
40
+ >
41
+ {open
42
+ ? decodeURIComponent(attributes.toggle_active_text)
43
+ : decodeURIComponent(attributes.toggle_inactive_text)}
44
+ </a>
45
+ </Disclaimer>
46
+
47
+ <TextContainer style={{ height: open ? getHeight() : '0px' }}>
48
+ <p ref={ref}>{decodeURIComponent(attributes.disclaimer_full_text)}</p>
49
+ </TextContainer>
50
+ </Container>
51
+ );
52
+ };
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import { AffiliateLinkDisclaimer } from '../AffiliateLinkDisclaimer';
4
+ import '@testing-library/jest-dom';
5
+
6
+ describe('AffiliateLinkDisclaimer Component', () => {
7
+ const defaultProps = {
8
+ attributes: {
9
+ disclaimer_text: 'This%20article%20contains%20affiliate%20links.',
10
+ toggle_active_text: 'Show%20less',
11
+ toggle_inactive_text: 'Show%20more',
12
+ disclaimer_full_text:
13
+ 'Our%20travel%20journalism%20is%20written%20and%20edited%20by%20independent%20experts%20to%20inform%20and%20advise.'
14
+ }
15
+ };
16
+
17
+ it('renders the disclaimer text and toggle link initially', () => {
18
+ render(<AffiliateLinkDisclaimer attributes={defaultProps.attributes} />);
19
+
20
+ // Check the disclaimer text
21
+ const disclaimerText = screen.getByText(
22
+ 'This article contains affiliate links.'
23
+ );
24
+ expect(disclaimerText).toBeInTheDocument();
25
+
26
+ // Check the initial toggle text
27
+ const toggleLink = screen.getByText('Show more');
28
+ expect(toggleLink).toBeInTheDocument();
29
+ expect(toggleLink).toHaveAttribute('href', '#');
30
+ });
31
+
32
+ it('expands and collapses full text when the toggle link is clicked', () => {
33
+ render(<AffiliateLinkDisclaimer attributes={defaultProps.attributes} />);
34
+
35
+ const toggleLink = screen.getByText('Show more');
36
+ const fullText = screen.getByText(
37
+ 'Our travel journalism is written and edited by independent experts to inform and advise.'
38
+ );
39
+
40
+ // Click to expand
41
+ fireEvent.click(toggleLink);
42
+
43
+ // Expect the toggle text to change to 'Show less'
44
+ expect(screen.getByText('Show less')).toBeInTheDocument();
45
+
46
+ // Click to collapse
47
+ fireEvent.click(screen.getByText('Show less'));
48
+
49
+ // Expect toggle text to change back to 'Show more'
50
+ expect(screen.getByText('Show more')).toBeInTheDocument();
51
+
52
+ // Expect the full text container to collapse
53
+ expect(fullText.parentElement).toHaveStyle('height: 0px');
54
+ });
55
+
56
+ it('decodes URI-encoded text correctly', () => {
57
+ render(<AffiliateLinkDisclaimer attributes={defaultProps.attributes} />);
58
+
59
+ // Check all decoded content
60
+ expect(
61
+ screen.getByText('This article contains affiliate links.')
62
+ ).toBeInTheDocument();
63
+ expect(screen.getByText('Show more')).toBeInTheDocument();
64
+ expect(
65
+ screen.getByText(
66
+ 'Our travel journalism is written and edited by independent experts to inform and advise.'
67
+ )
68
+ ).toBeInTheDocument();
69
+ });
70
+ });
@@ -0,0 +1,77 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const Container = styled.div`
4
+ height: auto;
5
+ border-top: 1px solid #ccc;
6
+ border-bottom: 1px solid #ccc;
7
+ margin: 30px 0 60px 0;
8
+ padding: 1px 1px 10px;
9
+ `;
10
+
11
+ export const Disclaimer = styled.div`
12
+ text-align: center;
13
+ padding: 14px 0;
14
+ margin: 5px 0;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+
19
+ .shortcode-disclaimer_text {
20
+ display: inline;
21
+ clear: both;
22
+ font-size: 1.8rem;
23
+ line-height: 1.5;
24
+ color: #1d1d1b;
25
+ padding: 0;
26
+ font-weight: 400;
27
+ margin: 0;
28
+ }
29
+
30
+ .shortcode-disclaimer__toggle {
31
+ font-weight: 400;
32
+ word-wrap: break-word;
33
+ margin-left: 8px;
34
+ font-family: Roboto, sans-serif;
35
+ font-size: 12px;
36
+ line-height: 1.5;
37
+ font-weight: 400;
38
+ color: rgb(105, 105, 105) !important;
39
+ font-style: normal;
40
+ text-decoration: none !important;
41
+ display: inline-flex;
42
+ margin-top: 5px;
43
+ }
44
+
45
+ .shortcode-disclaimer__toggle:after {
46
+ transition: transform 0.2s linear;
47
+ content: '';
48
+ display: inline-block;
49
+ background-image: url('https://www.thetimes.co.uk/travel/wp-content/themes/tnl-travel-refactor/dist/assets/css/svg/arrow-affiliate.svg');
50
+ background-repeat: no-repeat;
51
+ background-position: 50%;
52
+ background-size: cover;
53
+ height: 16px;
54
+ width: 16px;
55
+ margin-left: 2px;
56
+ margin-top: 2px;
57
+ -webkit-transform: rotate(180deg);
58
+ }
59
+ `;
60
+
61
+ export const TextContainer = styled.div`
62
+ overflow: hidden;
63
+ transition: height 0.3s ease-in-out;
64
+
65
+ p {
66
+ text-align: center;
67
+ margin: 0 !important;
68
+ clear: both;
69
+ font-size: 1.8rem;
70
+ line-height: 1.6;
71
+ color: rgb(105, 105, 105) !important;
72
+ padding: 0 5px 30px;
73
+ font-weight: 400;
74
+ font-family: TimesDigitalW04-Regular, TimesDigitalW04-Regular-fallback,
75
+ serif;
76
+ }
77
+ `;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import { Link } from './styles';
3
+
4
+ interface AttributesProps {
5
+ url: string;
6
+ target?: string;
7
+ text: string;
8
+ }
9
+
10
+ interface RootProps {
11
+ attributes?: AttributesProps;
12
+ }
13
+
14
+ export const CtaButton: React.FC<RootProps> = props => {
15
+ const attributes = props.attributes;
16
+
17
+ return attributes ? (
18
+ <Link
19
+ href={attributes.url}
20
+ target={attributes.target || '_blank'}
21
+ rel="nofollow"
22
+ >
23
+ {attributes.text}
24
+ </Link>
25
+ ) : null;
26
+ };
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { CtaButton } from '../CtaButton';
4
+
5
+ describe('CtaButton Component', () => {
6
+ const defaultProps = {
7
+ url: 'https://www.example.com',
8
+ target: '_blank',
9
+ text: 'Book a Stay'
10
+ };
11
+
12
+ it('does not render a button if attributes are not provided', () => {
13
+ const { container } = render(<CtaButton attributes={defaultProps} />);
14
+ const button = container.querySelector('button');
15
+ expect(button).toBeNull();
16
+ });
17
+ });
@@ -0,0 +1,17 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const Link = styled.a`
4
+ width: fit-content;
5
+ height: 48px;
6
+ font-family: Roboto, sans-serif;
7
+ font-size: 16px;
8
+ font-weight: 500;
9
+ border: none;
10
+ border-radius: 0;
11
+ padding: 12px 16px;
12
+ background-color: #005c8a;
13
+ color: #ffffff;
14
+ display: flex;
15
+ text-decoration: none;
16
+ align-items: center;
17
+ `;
package/src/index.ts CHANGED
@@ -123,6 +123,8 @@ export {
123
123
  FeaturesCarousel
124
124
  } from './components/features-carousel/FeaturesCarousel';
125
125
 
126
+ // Button Components
127
+ export { CtaButton } from './components/cta-button/CtaButton';
126
128
  export { SocialMediaEmbed } from './components/social-embed/SocialMediaEmbed';
127
129
 
128
130
  // Contexts
@@ -130,3 +132,7 @@ export {
130
132
  useSocialEmbedsContext,
131
133
  SocialEmbedsProvider
132
134
  } from './contexts/SocialEmbedsProvider';
135
+
136
+ export {
137
+ AffiliateLinkDisclaimer
138
+ } from './components/affiliate-link-disclaimer/AffiliateLinkDisclaimer';