@guardian/interactive-component-library 0.4.3 → 0.4.4

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.
@@ -1,5 +1,6 @@
1
- export function Ticker({ maxItems, onStateChange, children }: {
1
+ export function Ticker({ maxItems, onStateChange, verticalAtMobile, children, }: {
2
2
  maxItems?: number;
3
3
  onStateChange: any;
4
+ verticalAtMobile?: boolean;
4
5
  children: any;
5
6
  }): import("preact").JSX.Element;
@@ -1,19 +1,20 @@
1
1
  import { jsxs, jsx } from "preact/jsx-runtime";
2
2
  import { toChildArray } from "preact";
3
- import { useState, useMemo, useRef, useLayoutEffect } from "preact/hooks";
4
- import { Gradient } from "./gradient/index.js";
5
- import "../../particles/info-button/index.js";
6
- import "../../particles/relative-time-sentence/index.js";
7
- import { ArrowButton } from "../../particles/arrow-button/index.js";
8
- import { Button } from "../../particles/button/index.js";
3
+ import { useState, useRef, useLayoutEffect, useMemo, useEffect, useCallback } from "preact/hooks";
4
+ import { useContainerSize } from "../../../shared/hooks/useContainerSize.js";
5
+ import { TickerControlsDesktop } from "./lib/TickerControlsDesktop.js";
6
+ import { TickerControlsMobileVertical } from "./lib/TickerControlsMobileVertical.js";
7
+ import { getOffsetDistance } from "./lib/helpers/tickerHelper.js";
9
8
  import styles from "./style.module.scss.js";
10
- function Ticker({ maxItems = 20, onStateChange, children }) {
9
+ function Ticker({
10
+ maxItems = 20,
11
+ onStateChange,
12
+ verticalAtMobile = false,
13
+ children
14
+ }) {
11
15
  const [pageIndex, setPageIndex] = useState(0);
12
- const [pageWidth, setPageWidth] = useState(0);
16
+ const [scrollElWidth, setScrollElWidth] = useState(0);
13
17
  const [numberOfPages, setNumberOfPages] = useState(0);
14
- const offsetWidth = useMemo(() => {
15
- return -pageIndex * (pageWidth || 0);
16
- }, [pageIndex, pageWidth]);
17
18
  const tickerRef = useRef();
18
19
  const tickerItemsRef = useRef();
19
20
  const tickerScrollRef = useRef();
@@ -21,20 +22,33 @@ function Ticker({ maxItems = 20, onStateChange, children }) {
21
22
  const [hideButtons, setHideButtons] = useState(false);
22
23
  const [expanded, setExpanded] = useState(false);
23
24
  const childArray = toChildArray(children);
25
+ const containerSize = useContainerSize(tickerRef);
26
+ const mobLandscapeW = 480;
27
+ const containerWidth = containerSize ? containerSize.width : 600;
28
+ const isMobile = containerWidth < mobLandscapeW;
29
+ const pageWidthModifier = 0.75;
24
30
  useLayoutEffect(() => {
25
31
  const tickerItemsContainer = tickerItemsRef.current;
26
- const pageWidth2 = tickerItemsContainer.clientWidth * 0.75;
27
- setPageWidth(pageWidth2);
28
- const numberOfPages2 = Math.ceil(
29
- tickerScrollRef.current.scrollWidth / pageWidth2
30
- );
32
+ const containerWidth2 = tickerItemsContainer.clientWidth * pageWidthModifier;
33
+ const tickerScrollEl = tickerScrollRef.current;
34
+ setScrollElWidth(tickerScrollEl.scrollWidth);
35
+ const numberOfPages2 = tickerScrollEl.scrollWidth / containerWidth2;
31
36
  setNumberOfPages(numberOfPages2);
32
37
  }, [childArray]);
33
- useLayoutEffect(() => {
38
+ const offsetWidth = useMemo(() => {
39
+ let res = getOffsetDistance(
40
+ pageIndex,
41
+ numberOfPages,
42
+ scrollElWidth,
43
+ pageWidthModifier
44
+ );
45
+ return res;
46
+ }, [pageIndex, numberOfPages, scrollElWidth]);
47
+ useEffect(() => {
34
48
  const hideButtons2 = childArray.length < 4;
35
49
  setHideButtons(hideButtons2);
36
50
  }, [childArray]);
37
- function toggleExpandedState() {
51
+ const toggleExpandedState = useCallback(() => {
38
52
  if (expanded) {
39
53
  tickerRef.current.scrollIntoView({ behavior: "smooth", alignToTop: true });
40
54
  }
@@ -43,54 +57,44 @@ function Ticker({ maxItems = 20, onStateChange, children }) {
43
57
  if (onStateChange) onStateChange({ expanded: newState });
44
58
  return newState;
45
59
  });
46
- }
60
+ }, [expanded, onStateChange]);
47
61
  return /* @__PURE__ */ jsxs(
48
62
  "div",
49
63
  {
50
64
  ref: tickerRef,
51
- className: styles.ticker,
65
+ className: verticalAtMobile ? styles.tickerVertical : styles.ticker,
52
66
  style: `--ticker-offset: ${offsetWidth}px;`,
53
67
  "data-expanded": expanded,
54
68
  children: [
55
- /* @__PURE__ */ jsx("div", { ref: tickerItemsRef, className: styles.tickerItems, children: /* @__PURE__ */ jsx("div", { ref: tickerScrollRef, className: styles.tickerScroll, children: childArray.map((child, index) => {
56
- var _a;
57
- return /* @__PURE__ */ jsx("div", { className: styles.tickerItem, children: child }, ((_a = child == null ? void 0 : child.props) == null ? void 0 : _a.id) ?? index);
58
- }) }) }),
59
- /* @__PURE__ */ jsxs(
69
+ /* @__PURE__ */ jsx("div", { ref: tickerItemsRef, className: styles.tickerItems, children: /* @__PURE__ */ jsx(
60
70
  "div",
61
71
  {
62
- ref: controlsRef,
63
- className: styles.controls,
64
- style: hideButtons && { display: "none" },
65
- children: [
66
- /* @__PURE__ */ jsx("div", { className: styles.gradient, children: /* @__PURE__ */ jsx(Gradient, {}) }),
67
- /* @__PURE__ */ jsxs("div", { className: styles.buttons, children: [
68
- /* @__PURE__ */ jsx(
69
- ArrowButton,
70
- {
71
- onClick: () => setPageIndex((d) => d + 1),
72
- disabled: pageIndex >= numberOfPages - 2
73
- }
74
- ),
75
- /* @__PURE__ */ jsx(
76
- ArrowButton,
77
- {
78
- direction: "left",
79
- onClick: () => setPageIndex((d) => d - 1),
80
- disabled: pageIndex <= 0
81
- }
82
- )
83
- ] }),
84
- /* @__PURE__ */ jsx("div", { className: styles.button, children: /* @__PURE__ */ jsx(
85
- Button,
86
- {
87
- type: "regular",
88
- styles: { buttonInner: styles.buttonInner },
89
- onClick: toggleExpandedState,
90
- children: expanded ? "Show fewer" : `Show ${maxItems} most recent`
91
- }
92
- ) })
93
- ]
72
+ ref: tickerScrollRef,
73
+ className: verticalAtMobile ? styles.tickerScrollVertical : styles.tickerScroll,
74
+ children: childArray.map((child, index) => {
75
+ var _a;
76
+ return /* @__PURE__ */ jsx("div", { className: styles.tickerItem, children: child }, ((_a = child == null ? void 0 : child.props) == null ? void 0 : _a.id) ?? index);
77
+ })
78
+ }
79
+ ) }),
80
+ isMobile && !verticalAtMobile && /* @__PURE__ */ jsx("div", { className: styles.gradientHorizontal }),
81
+ isMobile && verticalAtMobile && /* @__PURE__ */ jsx(
82
+ TickerControlsMobileVertical,
83
+ {
84
+ hideButtons,
85
+ controlsRef,
86
+ toggleExpandedState,
87
+ buttonText: expanded ? "Show fewer" : `Show ${maxItems} most recent`
88
+ }
89
+ ),
90
+ !isMobile && /* @__PURE__ */ jsx(
91
+ TickerControlsDesktop,
92
+ {
93
+ hideButtons,
94
+ controlsRef,
95
+ setPageIndex,
96
+ pageIndex,
97
+ numberOfPages
94
98
  }
95
99
  )
96
100
  ]
@@ -0,0 +1,7 @@
1
+ export function TickerControlsDesktop({ hideButtons, controlsRef, setPageIndex, pageIndex, numberOfPages, }: {
2
+ hideButtons: any;
3
+ controlsRef: any;
4
+ setPageIndex: any;
5
+ pageIndex: any;
6
+ numberOfPages: any;
7
+ }): import("preact").JSX.Element;
@@ -0,0 +1,52 @@
1
+ import { jsxs, jsx } from "preact/jsx-runtime";
2
+ import { Gradient } from "../gradient/index.js";
3
+ import "preact/hooks";
4
+ import "../../../particles/info-button/index.js";
5
+ import "../../../particles/relative-time-sentence/index.js";
6
+ import { ArrowButton } from "../../../particles/arrow-button/index.js";
7
+ import styles from "../style.module.scss.js";
8
+ const TickerControlsDesktop = ({
9
+ hideButtons,
10
+ controlsRef,
11
+ setPageIndex,
12
+ pageIndex,
13
+ numberOfPages
14
+ }) => {
15
+ const getDisabled = (indx, numPages) => {
16
+ if (indx === 0 && numPages > 1) {
17
+ return false;
18
+ }
19
+ return indx >= Math.floor(numPages) - 1;
20
+ };
21
+ return /* @__PURE__ */ jsxs(
22
+ "div",
23
+ {
24
+ ref: controlsRef,
25
+ className: styles.controls,
26
+ style: hideButtons && { display: "none" },
27
+ children: [
28
+ /* @__PURE__ */ jsx("div", { className: styles.gradient, children: /* @__PURE__ */ jsx(Gradient, {}) }),
29
+ /* @__PURE__ */ jsxs("div", { className: styles.buttons, children: [
30
+ /* @__PURE__ */ jsx(
31
+ ArrowButton,
32
+ {
33
+ onClick: () => setPageIndex((d) => d + 1),
34
+ disabled: getDisabled(pageIndex, numberOfPages)
35
+ }
36
+ ),
37
+ /* @__PURE__ */ jsx(
38
+ ArrowButton,
39
+ {
40
+ direction: "left",
41
+ onClick: () => setPageIndex((d) => d - 1),
42
+ disabled: pageIndex <= 0
43
+ }
44
+ )
45
+ ] })
46
+ ]
47
+ }
48
+ );
49
+ };
50
+ export {
51
+ TickerControlsDesktop
52
+ };
@@ -0,0 +1,6 @@
1
+ export function TickerControlsMobileVertical({ hideButtons, controlsRef, toggleExpandedState, buttonText, }: {
2
+ hideButtons: any;
3
+ controlsRef: any;
4
+ toggleExpandedState: any;
5
+ buttonText: any;
6
+ }): import("preact").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsxs, jsx } from "preact/jsx-runtime";
2
+ import { Gradient } from "../gradient/index.js";
3
+ import "preact/hooks";
4
+ import "../../../particles/info-button/index.js";
5
+ import "../../../particles/relative-time-sentence/index.js";
6
+ import { Button } from "../../../particles/button/index.js";
7
+ import styles from "../style.module.scss.js";
8
+ const TickerControlsMobileVertical = ({
9
+ hideButtons,
10
+ controlsRef,
11
+ toggleExpandedState,
12
+ buttonText
13
+ }) => {
14
+ return /* @__PURE__ */ jsxs(
15
+ "div",
16
+ {
17
+ ref: controlsRef,
18
+ className: styles.controls,
19
+ style: hideButtons && { display: "none" },
20
+ children: [
21
+ /* @__PURE__ */ jsx("div", { className: styles.gradient, children: /* @__PURE__ */ jsx(Gradient, {}) }),
22
+ /* @__PURE__ */ jsx("div", { className: styles.button, children: /* @__PURE__ */ jsx(
23
+ Button,
24
+ {
25
+ type: "regular",
26
+ styles: { buttonInner: styles.buttonInner },
27
+ onClick: toggleExpandedState,
28
+ children: buttonText
29
+ }
30
+ ) })
31
+ ]
32
+ }
33
+ );
34
+ };
35
+ export {
36
+ TickerControlsMobileVertical
37
+ };
@@ -0,0 +1 @@
1
+ export function getOffsetDistance(pageIndex: any, numberOfPages: any, scrollElWidth: any, pageWidthModifier: any): number;
@@ -0,0 +1,17 @@
1
+ const getOffsetDistance = (pageIndex, numberOfPages, scrollElWidth, pageWidthModifier) => {
2
+ const onlyOnePush = numberOfPages > 1 && numberOfPages <= 2;
3
+ const defaultPushDistance = onlyOnePush ? scrollElWidth / numberOfPages * pageWidthModifier : scrollElWidth / Math.floor(numberOfPages);
4
+ const finalPushDistance = defaultPushDistance * (numberOfPages % 1);
5
+ if (pageIndex === 0) {
6
+ return 0;
7
+ } else if (pageIndex === 1 && onlyOnePush) {
8
+ return -finalPushDistance;
9
+ } else if (pageIndex === Math.floor(numberOfPages) - 1) {
10
+ return -pageIndex * defaultPushDistance + finalPushDistance;
11
+ } else if (pageIndex < Math.floor(numberOfPages)) {
12
+ return -pageIndex * defaultPushDistance;
13
+ }
14
+ };
15
+ export {
16
+ getOffsetDistance
17
+ };
@@ -1,19 +1,25 @@
1
- const ticker = "_ticker_15ezr_9";
2
- const tickerItems = "_tickerItems_15ezr_21";
3
- const tickerScroll = "_tickerScroll_15ezr_26";
4
- const tickerItem = "_tickerItem_15ezr_21";
5
- const controls = "_controls_15ezr_48";
6
- const gradient = "_gradient_15ezr_63";
7
- const buttons = "_buttons_15ezr_75";
8
- const button = "_button_15ezr_75";
9
- const buttonInner = "_buttonInner_15ezr_101";
1
+ const tickerVertical = "_tickerVertical_ozku7_9";
2
+ const ticker = "_ticker_ozku7_9";
3
+ const tickerItems = "_tickerItems_ozku7_27";
4
+ const tickerScrollVertical = "_tickerScrollVertical_ozku7_32";
5
+ const tickerScroll = "_tickerScroll_ozku7_32";
6
+ const tickerItem = "_tickerItem_ozku7_27";
7
+ const controls = "_controls_ozku7_75";
8
+ const gradient = "_gradient_ozku7_90";
9
+ const gradientHorizontal = "_gradientHorizontal_ozku7_102";
10
+ const buttons = "_buttons_ozku7_116";
11
+ const button = "_button_ozku7_116";
12
+ const buttonInner = "_buttonInner_ozku7_142";
10
13
  const styles = {
14
+ tickerVertical,
11
15
  ticker,
12
16
  tickerItems,
17
+ tickerScrollVertical,
13
18
  tickerScroll,
14
19
  tickerItem,
15
20
  controls,
16
21
  gradient,
22
+ gradientHorizontal,
17
23
  buttons,
18
24
  button,
19
25
  buttonInner
@@ -25,8 +31,11 @@ export {
25
31
  controls,
26
32
  styles as default,
27
33
  gradient,
34
+ gradientHorizontal,
28
35
  ticker,
29
36
  tickerItem,
30
37
  tickerItems,
31
- tickerScroll
38
+ tickerScroll,
39
+ tickerScrollVertical,
40
+ tickerVertical
32
41
  };
package/dist/style.css CHANGED
@@ -3199,24 +3199,30 @@ body.android {
3199
3199
  --top-inset: 58px;
3200
3200
  }
3201
3201
 
3202
- ._ticker_15ezr_9 {
3202
+ ._tickerVertical_ozku7_9 {
3203
3203
  position: relative;
3204
3204
  padding-bottom: 44px;
3205
3205
  --ticker-item-width: 100%;
3206
3206
  }
3207
3207
  @media (min-width: 30em) {
3208
- ._ticker_15ezr_9 {
3209
- --ticker-item-width: 200px;
3208
+ ._tickerVertical_ozku7_9 {
3209
+ --ticker-item-width: auto;
3210
3210
  padding: 0;
3211
3211
  }
3212
3212
  }
3213
3213
 
3214
- ._tickerItems_15ezr_21 {
3214
+ ._ticker_ozku7_9 {
3215
+ position: relative;
3216
+ --ticker-item-width: 200px;
3217
+ padding: 0;
3218
+ }
3219
+
3220
+ ._tickerItems_ozku7_27 {
3215
3221
  width: 100%;
3216
3222
  overflow: clip;
3217
3223
  }
3218
3224
 
3219
- ._tickerScroll_15ezr_26 {
3225
+ ._tickerScrollVertical_ozku7_32 {
3220
3226
  display: flex;
3221
3227
  flex-direction: column;
3222
3228
  row-gap: var(--space-2);
@@ -3225,7 +3231,7 @@ body.android {
3225
3231
  overflow: visible;
3226
3232
  }
3227
3233
  @media (min-width: 30em) {
3228
- ._tickerScroll_15ezr_26 {
3234
+ ._tickerScrollVertical_ozku7_32 {
3229
3235
  flex-direction: row;
3230
3236
  column-gap: var(--space-2);
3231
3237
  transform: translateX(var(--ticker-offset));
@@ -3233,19 +3239,40 @@ body.android {
3233
3239
  }
3234
3240
  }
3235
3241
 
3236
- ._tickerItem_15ezr_21 {
3242
+ ._tickerScroll_ozku7_32 {
3243
+ display: flex;
3244
+ flex-direction: row;
3245
+ flex-wrap: nowrap;
3246
+ scrollbar-width: none;
3247
+ overflow-x: scroll;
3248
+ column-gap: var(--space-2);
3249
+ transform: translateX(var(--ticker-offset));
3250
+ transition: transform 0.5s ease-in-out;
3251
+ width: auto;
3252
+ padding-right: 50px;
3253
+ }
3254
+ ._tickerScroll_ozku7_32:-webkit-scrollbar {
3255
+ display: none; /* for Chrome, Safari, and Opera */
3256
+ }
3257
+ @media (min-width: 30em) {
3258
+ ._tickerScroll_ozku7_32 {
3259
+ width: fit-content;
3260
+ }
3261
+ }
3262
+
3263
+ ._tickerItem_ozku7_27 {
3237
3264
  width: var(--ticker-item-width);
3238
3265
  flex-shrink: 0;
3239
3266
  }
3240
3267
 
3241
- ._controls_15ezr_48 {
3268
+ ._controls_ozku7_75 {
3242
3269
  position: absolute;
3243
3270
  bottom: 0;
3244
3271
  width: 100%;
3245
3272
  height: 130px;
3246
3273
  }
3247
3274
  @media (min-width: 30em) {
3248
- ._controls_15ezr_48 {
3275
+ ._controls_ozku7_75 {
3249
3276
  top: 0;
3250
3277
  right: 0;
3251
3278
  width: 86px;
@@ -3253,23 +3280,37 @@ body.android {
3253
3280
  }
3254
3281
  }
3255
3282
 
3256
- ._gradient_15ezr_63 {
3283
+ ._gradient_ozku7_90 {
3257
3284
  width: 100%;
3258
3285
  height: 86px;
3259
3286
  }
3260
3287
  @media (min-width: 30em) {
3261
- ._gradient_15ezr_63 {
3288
+ ._gradient_ozku7_90 {
3262
3289
  width: auto;
3263
3290
  height: 100%;
3264
3291
  right: 0;
3265
3292
  }
3266
3293
  }
3267
3294
 
3268
- ._buttons_15ezr_75 {
3295
+ ._gradientHorizontal_ozku7_102 {
3296
+ width: 60px;
3297
+ height: 100%;
3298
+ right: 0;
3299
+ top: 0;
3300
+ position: absolute;
3301
+ background: linear-gradient(to right, transparent 0%, var(--tertiary-bg-color) 80%, var(--tertiary-bg-color));
3302
+ }
3303
+ @media (min-width: 30em) {
3304
+ ._gradientHorizontal_ozku7_102 {
3305
+ width: auto;
3306
+ }
3307
+ }
3308
+
3309
+ ._buttons_ozku7_116 {
3269
3310
  display: none;
3270
3311
  }
3271
3312
  @media (min-width: 30em) {
3272
- ._buttons_15ezr_75 {
3313
+ ._buttons_ozku7_116 {
3273
3314
  position: absolute;
3274
3315
  top: 0;
3275
3316
  right: var(--space-5);
@@ -3280,36 +3321,36 @@ body.android {
3280
3321
  }
3281
3322
  }
3282
3323
 
3283
- ._button_15ezr_75 {
3324
+ ._button_ozku7_116 {
3284
3325
  min-height: 40px;
3285
3326
  background-color: var(--tertiary-bg-color);
3286
3327
  padding-bottom: 20px;
3287
3328
  }
3288
3329
  @media (min-width: 30em) {
3289
- ._button_15ezr_75 {
3330
+ ._button_ozku7_116 {
3290
3331
  display: none;
3291
3332
  }
3292
3333
  }
3293
3334
 
3294
- ._buttonInner_15ezr_101 {
3335
+ ._buttonInner_ozku7_142 {
3295
3336
  background-color: var(--tertiary-bg-color);
3296
3337
  }
3297
3338
 
3298
- ._ticker_15ezr_9[data-expanded=true] {
3339
+ ._tickerVertical_ozku7_9[data-expanded=true] {
3299
3340
  padding-bottom: 0;
3300
3341
  }
3301
3342
 
3302
- ._ticker_15ezr_9[data-expanded=true] ._tickerScroll_15ezr_26 {
3343
+ ._tickerVertical_ozku7_9[data-expanded=true] ._tickerScrollVertical_ozku7_32 {
3303
3344
  max-height: fit-content;
3304
3345
  margin-bottom: -40px;
3305
3346
  }
3306
3347
 
3307
- ._ticker_15ezr_9[data-expanded=true] ._controls_15ezr_48 {
3348
+ ._tickerVertical_ozku7_9[data-expanded=true] ._controls_ozku7_75 {
3308
3349
  position: sticky;
3309
3350
  margin-top: -40px;
3310
3351
  }
3311
3352
 
3312
- ._ticker_15ezr_9[data-expanded=true] ._button_15ezr_75 {
3353
+ ._tickerVertical_ozku7_9[data-expanded=true] ._button_ozku7_116 {
3313
3354
  position: absolute;
3314
3355
  width: 100%;
3315
3356
  left: 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@guardian/interactive-component-library",
3
3
  "private": false,
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "packageManager": "pnpm@8.4.0",
6
6
  "repository": {
7
7
  "type": "git",