@comicrelief/component-library 8.77.4 → 8.77.6

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.
@@ -4,6 +4,8 @@ import Picture from '../../../Atoms/Picture/Picture';
4
4
  import Link from '../../../Atoms/Link/Link';
5
5
  import ArrowIcon from '../../shared/ctaArrow/ArrowIcon';
6
6
  import altCtaUnderline from '../../../../theme/shared/assets/alt_cta_underline.svg';
7
+ import { CtaTextUnderline } from '../../shared/ctaText/ctaText.style';
8
+
7
9
  import {
8
10
  CardLink,
9
11
  ImageWrapper,
@@ -12,11 +14,11 @@ import {
12
14
  CardLabel,
13
15
  CTA,
14
16
  CTAText,
15
- CTATextUnderline,
16
17
  ArrowIconOuter,
17
18
  ArrowIconInner,
18
19
  ArrowIconWrapper,
19
- CardWrapper
20
+ CardWrapper,
21
+ CardLinkContent
20
22
  } from './CTACard.style';
21
23
 
22
24
  const CTACard = ({
@@ -51,16 +53,16 @@ const CTACard = ({
51
53
  $isCarousel={isCarousel}
52
54
  $isFullWidth={effectiveIsFullWidth}
53
55
  $columns={columns}
54
- $hasLink={hasLink}
55
56
  >
56
57
  <CardLink
57
58
  as={hasLink ? Link : 'div'}
58
59
  {...(hasLink ? { href: link, target, rel: external } : {})}
59
60
  $isCarousel={isCarousel}
60
61
  $isSingleCard={isSingleCard}
61
- $isInteractive={hasLink}
62
+ $hasLink={hasLink}
62
63
  >
63
- {imageLow && (
64
+ <CardLinkContent>
65
+ {imageLow && (
64
66
  <ImageWrapper $isSingleCard={isSingleCard}>
65
67
  <Picture
66
68
  alt={description}
@@ -72,21 +74,21 @@ const CTACard = ({
72
74
  height="100%"
73
75
  />
74
76
  </ImageWrapper>
75
- )}
76
- <CopyAndLinkSection $isSingleCard={isSingleCard}>
77
- <Copy>
78
- {isSingleCard && label && (
77
+ )}
78
+ <CopyAndLinkSection $isSingleCard={isSingleCard}>
79
+ <Copy>
80
+ {isSingleCard && label && (
79
81
  <CardLabel>
80
82
  {label}
81
83
  </CardLabel>
82
- )}
83
- {body}
84
- </Copy>
85
- {linkLabel && (
84
+ )}
85
+ {body}
86
+ </Copy>
87
+ {linkLabel && (
86
88
  <CTA>
87
89
  <CTAText>
88
90
  {linkLabel}
89
- <CTATextUnderline
91
+ <CtaTextUnderline
90
92
  src={altCtaUnderline}
91
93
  alt=""
92
94
  aria-hidden="true"
@@ -100,8 +102,9 @@ const CTACard = ({
100
102
  </ArrowIconWrapper>
101
103
  </ArrowIconOuter>
102
104
  </CTA>
103
- )}
104
- </CopyAndLinkSection>
105
+ )}
106
+ </CopyAndLinkSection>
107
+ </CardLinkContent>
105
108
  </CardLink>
106
109
  </CardWrapper>
107
110
  );
@@ -4,6 +4,7 @@ import { breakpointValues } from '../../../../theme/shared/allBreakpoints';
4
4
  import fontHelper from '../../../../theme/crTheme/fontHelper';
5
5
  import defaultBoxShadow from '../../../../theme/shared/boxShadows';
6
6
  import { ArrowIconInner, ArrowIconOuter, ArrowIconWrapper } from '../../shared/ctaArrow/CtaArrowCircle.style';
7
+ import { CtaTextUnderline } from '../../shared/ctaText/ctaText.style';
7
8
 
8
9
  const ImageWrapper = styled.div`
9
10
  width: 100%;
@@ -54,25 +55,25 @@ const ImageWrapper = styled.div`
54
55
  const CTAText = styled.span`
55
56
  ${({ theme }) => fontHelper(theme, 'span')}
56
57
  color: ${({ theme }) => theme.color('red')};
57
-
58
- @media (min-width: ${breakpointValues.L}px) {
59
- color: ${({ theme }) => theme.color('grey_4')};
60
- }
61
58
  font-weight: bold;
62
59
  text-decoration: none;
63
60
  position: relative;
64
61
  display: inline-block;
62
+ transition: color 0.35s;
63
+
64
+ @media (min-width: ${breakpointValues.L}px) {
65
+ color: ${({ theme }) => theme.color('grey_4')};
66
+ }
65
67
  `;
66
68
 
67
- const CTATextUnderline = styled.img`
68
- height: 4px;
69
- width: 100%;
70
- position: absolute;
71
- left: 0;
72
- bottom: -5px;
73
- transition: opacity 0.15s 0.1s;
74
- opacity: 0;
75
- pointer-events: none;
69
+ const CardLinkContent = styled.div`
70
+ display: inherit;
71
+ flex-direction: inherit;
72
+ flex: inherit;
73
+ border-radius: inherit;
74
+ overflow: hidden;
75
+ transition: box-shadow 0.35s;
76
+ ${defaultBoxShadow()}
76
77
  `;
77
78
 
78
79
  // Card wrapper link - makes entire card clickable
@@ -84,12 +85,19 @@ const CardLink = styled.a`
84
85
  flex: 1 1 auto;
85
86
  background: transparent;
86
87
  border-radius: 1rem;
87
- ${defaultBoxShadow()}
88
88
  text-decoration: none;
89
- overflow: hidden;
90
- cursor: ${({ $isInteractive }) => ($isInteractive ? 'pointer' : 'default')};
89
+ cursor: ${({ $hasLink }) => ($hasLink ? 'pointer' : 'default')};
91
90
  box-sizing: border-box;
92
91
 
92
+ ${({ $hasLink }) => $hasLink && css`
93
+ &:hover,
94
+ &:focus {
95
+ ${CardLinkContent} {
96
+ ${defaultBoxShadow(true)}
97
+ }
98
+ }
99
+ `};
100
+
93
101
  // Side-by-side layout for single card desktop view
94
102
  ${({ $isSingleCard }) => $isSingleCard && css`
95
103
  @media ${({ theme }) => theme.breakpoints2026('M')} {
@@ -104,39 +112,67 @@ const CardLink = styled.a`
104
112
  }
105
113
  `}
106
114
 
107
- ${({ $isInteractive }) => $isInteractive && css`
108
- img {
115
+ ${({ $hasLink }) => $hasLink && css`
116
+ ${ImageWrapper} img {
109
117
  ${imageZoom({ initialScale: 1 })}
110
118
  }
111
119
 
112
- // Desktop-only hover effects
113
120
  @media ${({ theme }) => theme.allBreakpoints('M')} {
114
- ${bounceUpAnimation(true, 10, 1)};
115
- /* override the bounceUpAnimation transition */
116
- transition: transform 0.4s cubic-bezier(0.68, -1.15, 0.265, 2.35);
121
+ &:hover,
122
+ &:focus {
117
123
 
118
- &:hover {
119
- ${defaultBoxShadow(true)}
124
+ ${CTAText} {
125
+ color: ${({ theme }) => theme.color('red')};
126
+ }
127
+
128
+ ${CtaTextUnderline} {
129
+ opacity: 1;
130
+ }
120
131
 
132
+ ${ArrowIconWrapper} {
133
+ @media (min-width: ${breakpointValues.L}px) {
134
+ background: ${({ theme }) => theme.color('red')};
135
+ }
136
+
137
+ ${ArrowIconInner} {
138
+ transform: scale(1.2);
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ // Desktop-only hover/focus effects
145
+ @media ${({ theme }) => theme.allBreakpoints('L')} {
146
+
147
+ // Allow the nested content and its box-shadow to be completely visible when it's animated upwards:
148
+ overflow: visible;
149
+
150
+ // Using the 4th, optional 'target child' parameter to target CardLinkContent for the position animation
151
+ // rather than *this* element, to ensure we don't end up with that infinite bounce bug.
152
+ ${bounceUpAnimation(true, 10, 2, true)};
153
+
154
+ /* Override the transition initially set by bounceUpAnimation (targetting the same child) to tweaked some values,
155
+ while also ensuring we don't lose the previously-added box-shadow animation by re-adding it here: */
156
+ > ${CardLinkContent} {
157
+ transition: transform 0.35s cubic-bezier(0.68, -1.15, 0.265, 2.35), box-shadow 0.35s;
158
+ }
159
+
160
+ &:hover,
161
+ &:focus {
121
162
  ${ImageWrapper} img {
122
163
  ${imageZoom({ zoomed: true, finalScale: 1.06 })}
123
164
  }
124
165
 
125
166
  ${CTAText} {
126
- text-decoration: none;
127
- @media (min-width: ${breakpointValues.L}px) {
128
- color: ${({ theme }) => theme.color('red')};
129
- }
167
+ color: ${({ theme }) => theme.color('red')};
130
168
  }
131
169
 
132
- ${CTATextUnderline} {
170
+ ${CtaTextUnderline} {
133
171
  opacity: 1;
134
172
  }
135
173
 
136
174
  ${ArrowIconWrapper} {
137
- @media (min-width: ${breakpointValues.L}px) {
138
- background: ${({ theme }) => theme.color('red')};
139
- }
175
+ background: ${({ theme }) => theme.color('red')};
140
176
 
141
177
  ${ArrowIconInner} {
142
178
  transform: scale(1.2);
@@ -155,10 +191,6 @@ const CardWrapper = styled.div`
155
191
  align-self: stretch;
156
192
  border-radius: 1rem;
157
193
 
158
- ${({ $hasLink }) => (!$hasLink && css`
159
- ${defaultBoxShadow()}
160
- `)}
161
-
162
194
  // Full width mode - always full width, no constraints
163
195
  ${({ $isFullWidth }) => $isFullWidth && css`
164
196
  width: 100%;
@@ -305,16 +337,16 @@ const CTA = styled.div`
305
337
 
306
338
  export {
307
339
  CardLink,
308
- // CardsContainer,
309
340
  ImageWrapper,
310
341
  CopyAndLinkSection,
311
342
  Copy,
312
343
  CardLabel,
313
344
  CTA,
314
345
  CTAText,
315
- CTATextUnderline,
346
+ CtaTextUnderline,
316
347
  ArrowIconOuter,
317
348
  ArrowIconInner,
318
349
  ArrowIconWrapper,
319
- CardWrapper
350
+ CardWrapper,
351
+ CardLinkContent
320
352
  };
@@ -61,17 +61,16 @@ const HeroBanner = ({
61
61
  <CTAWrapper $variant={variant}>
62
62
  {(variant !== variants.TEXT_BANNER) ? (
63
63
  <>
64
- <CtaText className="cta">
64
+ <CtaText>
65
65
  {ctaText}
66
66
  <CtaTextUnderline
67
67
  src={altCtaUnderline}
68
68
  alt={ctaText}
69
- className="cta-text-underline"
70
69
  />
71
70
  </CtaText>
72
71
 
73
72
  <CtaIconWrapper>
74
- <ArrowIconWrapper $preventHoverColourChange>
73
+ <ArrowIconWrapper>
75
74
  <ArrowIconInner>
76
75
  <ArrowIcon />
77
76
  </ArrowIconInner>
@@ -4,6 +4,7 @@ import { bounceUpAnimation, playPauseReveal } from '../../../theme/shared/animat
4
4
  import defaultBoxShadow from '../../../theme/shared/boxShadows';
5
5
  import { ArrowIconWrapper, ArrowIconInner } from '../shared/ctaArrow/CtaArrowCircle.style';
6
6
  import Picture from '../../Atoms/Picture/Picture';
7
+ import { CtaText, CtaTextUnderline } from '../shared/ctaText/ctaText.style';
7
8
 
8
9
  // Lil helper function to streamline things somewhat:
9
10
  const handleVariant = variant => {
@@ -85,12 +86,13 @@ const MediaWrapper = styled.div`
85
86
 
86
87
  // Zoom the image in slightly by default so the 'bounce' animation doesn't cause issues
87
88
  transform: scale(1.02);
88
- transition: transform 0.4s cubic-bezier(0.68, -1.15, 0.265, 2.35);
89
+ transition: transform 0.35s cubic-bezier(0.68, -1.15, 0.265, 2.35);
89
90
 
90
91
  ${({ $variant }) => ($variant === variants.HALF_HEIGHT && 'min-height: 450px;')};
91
92
  }
92
93
 
93
- &:has(+ div a:hover) {
94
+ &:has(+ div a:hover),
95
+ &:has(+ div a:focus) {
94
96
  > div > img {
95
97
  transform: scale(1.04);
96
98
  }
@@ -196,9 +198,14 @@ const CTAWrapper = styled.div`
196
198
  ${({ $variant }) => ($variant !== variants.TEXT_BANNER && css`
197
199
  padding-right: 2.5rem;
198
200
 
199
- span {
201
+ ${CtaText} {
200
202
  font-weight: bold;
203
+ transition: color 0.35s;
201
204
  color: ${({ theme }) => theme.color('red')};
205
+
206
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
207
+ color: ${({ theme }) => theme.color('black')};
208
+ }
202
209
  }
203
210
  `)}
204
211
  `;
@@ -212,10 +219,30 @@ const HeroBannerLink = styled.a`
212
219
  ${defaultBoxShadow()}
213
220
  }
214
221
 
215
- &:hover {
222
+ &:hover,
223
+ &:focus {
216
224
  > div {
217
225
  ${defaultBoxShadow(true)}
218
226
  }
227
+
228
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
229
+ ${CtaText} {
230
+ color: ${({ theme }) => theme.color('red')};
231
+ }
232
+ }
233
+ }
234
+
235
+ @media ${({ theme }) => theme.allBreakpoints('M')} {
236
+ &:hover,
237
+ &:focus {
238
+ ${CtaTextUnderline} {
239
+ opacity: 1;
240
+ }
241
+
242
+ ${ArrowIconWrapper} ${ArrowIconInner} {
243
+ transform: scale(1.2);
244
+ }
245
+ }
219
246
  }
220
247
 
221
248
  @media ${({ theme }) => theme.breakpoints2026('L')} {
@@ -229,18 +256,10 @@ const HeroBannerLink = styled.a`
229
256
  width: 100%;
230
257
  }
231
258
 
232
- // Fade in the 'Alt CTA'-style squiggley underline:
233
- &:hover {
234
- img.cta-text-underline {
235
- opacity: 1;
236
- }
237
- }
238
- }
239
-
240
- @media ${({ theme }) => theme.allBreakpoints('M')} {
241
- &:hover {
242
- ${ArrowIconWrapper} ${ArrowIconInner} {
243
- transform: scale(1.2);
259
+ &:hover,
260
+ &:focus {
261
+ ${ArrowIconWrapper} {
262
+ background: ${({ theme }) => theme.color('red')};
244
263
  }
245
264
  }
246
265
  }
@@ -168,10 +168,11 @@ exports[`renders "Full Height Media" Hero Banner correctly 1`] = `
168
168
  object-position: top center;
169
169
  height: 100%;
170
170
  transform: scale(1.02);
171
- transition: transform 0.4s cubic-bezier(0.68,-1.15,0.265,2.35);
171
+ transition: transform 0.35s cubic-bezier(0.68,-1.15,0.265,2.35);
172
172
  }
173
173
 
174
- .c2:has(+ div a:hover) >div>img {
174
+ .c2:has(+ div a:hover) >div>img,
175
+ .c2:has(+ div a:focus) >div>img {
175
176
  transform: scale(1.04);
176
177
  }
177
178
  }
@@ -441,11 +442,12 @@ exports[`renders "Half Height Media" Hero Banner correctly 1`] = `
441
442
  object-position: top center;
442
443
  height: 100%;
443
444
  transform: scale(1.02);
444
- transition: transform 0.4s cubic-bezier(0.68,-1.15,0.265,2.35);
445
+ transition: transform 0.35s cubic-bezier(0.68,-1.15,0.265,2.35);
445
446
  min-height: 450px;
446
447
  }
447
448
 
448
- .c2:has(+ div a:hover) >div>img {
449
+ .c2:has(+ div a:hover) >div>img,
450
+ .c2:has(+ div a:focus) >div>img {
449
451
  transform: scale(1.04);
450
452
  }
451
453
  }
@@ -960,10 +962,11 @@ exports[`renders "Text Banner" Hero Banner correctly 2`] = `
960
962
  object-position: top center;
961
963
  height: 100%;
962
964
  transform: scale(1.02);
963
- transition: transform 0.4s cubic-bezier(0.68,-1.15,0.265,2.35);
965
+ transition: transform 0.35s cubic-bezier(0.68,-1.15,0.265,2.35);
964
966
  }
965
967
 
966
- .c2:has(+ div a:hover) >div>img {
968
+ .c2:has(+ div a:hover) >div>img,
969
+ .c2:has(+ div a:focus) >div>img {
967
970
  transform: scale(1.04);
968
971
  }
969
972
  }
@@ -1,11 +1,10 @@
1
- import styled, { css } from 'styled-components';
2
- import { breakpointValues } from '../../../../theme/shared/allBreakpoints';
1
+ import styled from 'styled-components';
3
2
 
4
3
  const ArrowIconInner = styled.span`
5
4
  display: inline-flex;
6
5
  align-items: center;
7
6
  justify-content: center;
8
- transition: transform 0.3s cubic-bezier(0.65, -0.19, 0.37, 1.16);
7
+ transition: transform 0.35s cubic-bezier(0.65, -0.19, 0.37, 1.16);
9
8
  transform: scale(1);
10
9
  transform-origin: center;
11
10
  `;
@@ -16,11 +15,9 @@ const ArrowIconWrapper = styled.div`
16
15
  border-radius: 50%;
17
16
  background: ${({ theme }) => theme.color('red')};
18
17
 
19
- ${({ $preventHoverColourChange }) => !$preventHoverColourChange && css`
20
- @media (min-width: ${breakpointValues.L}px) {
21
- background: ${({ theme }) => theme.color('grey_4')};
22
- }
23
- `}
18
+ @media ${({ theme }) => theme.breakpoints2026('L')} {
19
+ background: ${({ theme }) => theme.color('grey_4')};
20
+ }
24
21
 
25
22
  display: flex;
26
23
  align-items: center;
@@ -9,6 +9,7 @@ const CtaTextUnderline = styled.img`
9
9
  bottom: -5px;
10
10
  transition: opacity 0.15s 0.1s;
11
11
  opacity: 0;
12
+ pointer-events: none;
12
13
  `;
13
14
 
14
15
  const CtaIconWrapper = styled.div`
@@ -38,7 +38,7 @@ const StyledPopUp = styled.div`
38
38
  overflow: hidden;
39
39
  max-height: 350px;
40
40
  opacity: 1;
41
- animation: 0.4s ${props => props.fadeOpen} ease;
41
+ animation: 0.35s ${props => props.fadeOpen} ease;
42
42
  ${props => props.isClosed && css`
43
43
  animation: ${closeDuration}s ${props.fadeClose} ease forwards;
44
44
  `}
@@ -31,7 +31,7 @@ const pulseOut = keyframes`
31
31
  `;
32
32
 
33
33
  const pulseInAnimation = css`
34
- animation: ${pulseIn} 0.4s ease-in-out forwards;
34
+ animation: ${pulseIn} 0.35s ease-in-out forwards;
35
35
  `;
36
36
 
37
37
  const pulseOutAnimation = css`
@@ -120,7 +120,9 @@ const imageZoom = (params = {}) => {
120
120
 
121
121
  return !zoomed ? css`
122
122
  transform: scale(${initialScale});
123
- transition: transform 0.3s cubic-bezier(0.65, -0.19, 0.37, 1.16);
123
+ // Also including the box-shadow transform so we don't override it on breakpoints this
124
+ // transform is applied to (especially since we're generally using them together):
125
+ transition: transform 0.35s cubic-bezier(0.65, -0.19, 0.37, 1.16), box-shadow 0.35s;
124
126
  ` : css`
125
127
  transform: scale(${finalScale});
126
128
  `;