@financial-times/qanda-ui 1.0.0 → 1.0.2

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.
@@ -40,13 +40,17 @@ function Expander({ className = '', children = [], expandLabel = 'Expand', colla
40
40
  }
41
41
  const numberOfCharacters = element.textContent?.length || 300;
42
42
  const windowWidth = window.visualViewport?.width || window.innerWidth || 1200; // Fallback to desktop configuration.
43
- // Check if the element's height exceeds 3 lines for mobile or 1 line for above mobile
44
- // Note: This need to stay aligned with the CSS for the expander
45
- const mobileNumberOfCharacters = 100;
46
- const desktopNumberOfCharacters = 200;
43
+ // Approximate cut-off using character counts
44
+ // On average: ~176 characters on mobile (4 lines) and ~374 on desktop (5 lines)
45
+ // We set thresholds slightly higher (250 / 425) to ensure some text is hidden
46
+ // before showing the expander
47
+ // Note: This needs to stay aligned with the CSS for the expander
48
+ const mobileNumberOfCharacters = 225;
49
+ const desktopNumberOfCharacters = 425;
47
50
  const characterForExpander = windowWidth <= 490
48
51
  ? mobileNumberOfCharacters
49
52
  : desktopNumberOfCharacters;
53
+ // Show the expander if character count exceeds the threshold
50
54
  setExpander(numberOfCharacters > characterForExpander);
51
55
  };
52
56
  const debouncedCheck = debounce(checkNeedForExpanderButton, 250);
@@ -9,30 +9,11 @@ const useIsMobile_1 = require("../../hooks/useIsMobile");
9
9
  const comments_api_1 = require("../../services/comments-api");
10
10
  const updateComments_1 = require("../../store/updateComments");
11
11
  const react_1 = require("react");
12
- const o_utils_1 = require("@financial-times/o-utils");
13
12
  const QandaProvider_1 = require("../QandaProvider");
14
13
  const tracking_1 = require("../../utils/tracking");
15
14
  function LiveIndicator() {
16
15
  return ((0, jsx_runtime_1.jsx)("div", { className: "o-labels-indicator--live o3-type-label floating-action-bar__live-container", children: (0, jsx_runtime_1.jsx)("span", { className: "o-labels-indicator__status floating-action-bar__live-label", children: "Live" }) }));
17
16
  }
18
- function useAlignToQanda(ref) {
19
- (0, react_1.useEffect)(() => {
20
- const align = () => {
21
- const container = ref.current;
22
- const qandaRoot = container?.closest('.qanda-container');
23
- const target = qandaRoot?.querySelector('.qanda__blocks-container');
24
- if (!target || !container)
25
- return;
26
- const rect = target.getBoundingClientRect();
27
- container.style.left = `${rect.left}px`;
28
- container.style.width = `${rect.width}px`;
29
- };
30
- align();
31
- const debounced = (0, o_utils_1.debounce)(align, 100);
32
- window.addEventListener('resize', debounced);
33
- return () => window.removeEventListener('resize', debounced);
34
- }, [ref]);
35
- }
36
17
  function FloatingActionBar({ isLive = true }) {
37
18
  const dispatch = (0, react_redux_1.useDispatch)();
38
19
  const isMobile = (0, useIsMobile_1.useIsMobile)();
@@ -77,19 +58,17 @@ function FloatingActionBar({ isLive = true }) {
77
58
  dataTrackable: 'open_expert_drawer',
78
59
  };
79
60
  const view = showExpertView ? expertConfig : userConfig;
80
- const containerRef = (0, react_1.useRef)(null);
81
- useAlignToQanda(containerRef);
82
- return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, className: "floating-action-bar__container", "data-trackable": (0, tracking_1.trackingNS)('floating-action-bar'), children: [(0, jsx_runtime_1.jsx)("div", { className: "floating-action-bar__fade-overlay" }), (0, jsx_runtime_1.jsxs)("div", { className: "floating-action-bar", "data-testid": "floating-action-bar", children: [(0, jsx_runtime_1.jsx)("span", { className: "o3-visually-hidden", "aria-live": "polite", role: "alert", children: isLive ? 'This session is now live' : '' }), isLive && (0, jsx_runtime_1.jsx)(LiveIndicator, {}), (0, jsx_runtime_1.jsxs)("div", { className: "floating-action-bar__buttons", ref: modalOpenerRef, children: [(0, jsx_runtime_1.jsx)(o3_button_1.Button, { attributes: {
61
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "floating-action-bar__container", "data-trackable": (0, tracking_1.trackingNS)('floating-action-bar'), children: [(0, jsx_runtime_1.jsx)("div", { className: "floating-action-bar__fade-overlay" }), (0, jsx_runtime_1.jsxs)("div", { className: "floating-action-bar", "data-testid": "floating-action-bar", children: [(0, jsx_runtime_1.jsx)("span", { className: "o3-visually-hidden", "aria-live": "polite", role: "alert", children: isLive ? 'This session is now live' : '' }), isLive && (0, jsx_runtime_1.jsx)(LiveIndicator, {}), (0, jsx_runtime_1.jsxs)("div", { className: "floating-action-bar__buttons", ref: modalOpenerRef, children: [(0, jsx_runtime_1.jsx)(o3_button_1.Button, { attributes: {
83
62
  ariaLabel: view.description,
84
63
  'data-trackable': (0, tracking_1.trackingNS)(view.dataTrackable),
85
64
  'data-trackable-context-action': view.description,
86
65
  'data-testid': `floating-action-bar__${view.testIdSuffix}`,
87
66
  ariaHasPopup: 'dialog',
88
67
  }, label: view.description, type: "ghost", theme: "mono", icon: "edit", iconOnly: isMobile, onClick: view.onClick }), (0, jsx_runtime_1.jsx)(o3_button_1.Button, { attributes: {
89
- ariaLabel: 'Back to top',
90
- 'data-trackable': (0, tracking_1.trackingNS)('back_to_top'),
91
- 'data-trackable-context-trigger_action': 'Back to top',
92
- 'data-testid': 'floating-action-bar__back-to-top-button',
93
- }, label: "Back to top", type: "ghost", theme: "mono", icon: "scroll-to", iconOnly: isMobile, onClick: handleShowUpdates })] }), updatedComments?.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "floating-action-bar__updates-counter o3-type-detail", "aria-label": `${updatedComments.length} new ${updatedComments.length === 1 ? 'update' : 'updates'} available`, role: "status", "aria-live": "polite", children: updatedComments.length }))] })] }));
68
+ ariaLabel: 'Latest answers',
69
+ 'data-trackable': (0, tracking_1.trackingNS)('latest-answers'),
70
+ 'data-trackable-context-trigger_action': 'Latest answers',
71
+ 'data-testid': 'floating-action-bar__latest-answers-button',
72
+ }, label: "Latest answers", type: "ghost", theme: "mono", icon: "scroll-to", iconOnly: isMobile, onClick: handleShowUpdates })] }), updatedComments?.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "floating-action-bar__updates-counter o3-type-detail", "aria-label": `${updatedComments.length} new ${updatedComments.length === 1 ? 'update' : 'updates'} available`, role: "status", "aria-live": "polite", children: updatedComments.length }))] })] }));
94
73
  }
95
74
  exports.default = FloatingActionBar;
@@ -38,13 +38,17 @@ function Expander({ className = '', children = [], expandLabel = 'Expand', colla
38
38
  }
39
39
  const numberOfCharacters = element.textContent?.length || 300;
40
40
  const windowWidth = window.visualViewport?.width || window.innerWidth || 1200; // Fallback to desktop configuration.
41
- // Check if the element's height exceeds 3 lines for mobile or 1 line for above mobile
42
- // Note: This need to stay aligned with the CSS for the expander
43
- const mobileNumberOfCharacters = 100;
44
- const desktopNumberOfCharacters = 200;
41
+ // Approximate cut-off using character counts
42
+ // On average: ~176 characters on mobile (4 lines) and ~374 on desktop (5 lines)
43
+ // We set thresholds slightly higher (250 / 425) to ensure some text is hidden
44
+ // before showing the expander
45
+ // Note: This needs to stay aligned with the CSS for the expander
46
+ const mobileNumberOfCharacters = 225;
47
+ const desktopNumberOfCharacters = 425;
45
48
  const characterForExpander = windowWidth <= 490
46
49
  ? mobileNumberOfCharacters
47
50
  : desktopNumberOfCharacters;
51
+ // Show the expander if character count exceeds the threshold
48
52
  setExpander(numberOfCharacters > characterForExpander);
49
53
  };
50
54
  const debouncedCheck = debounce(checkNeedForExpanderButton, 250);
@@ -6,31 +6,12 @@ import { openExpertForm } from '../../store/expertSlice';
6
6
  import { useIsMobile } from '../../hooks/useIsMobile';
7
7
  import { useUpdatedComments } from '../../services/comments-api';
8
8
  import { updateComments } from '../../store/updateComments';
9
- import { useContext, useRef, useEffect } from 'react';
10
- import { debounce } from '@financial-times/o-utils';
9
+ import { useContext, useRef } from 'react';
11
10
  import { QandaContext } from '../QandaProvider';
12
11
  import { trackingNS } from '../../utils/tracking';
13
12
  function LiveIndicator() {
14
13
  return (_jsx("div", { className: "o-labels-indicator--live o3-type-label floating-action-bar__live-container", children: _jsx("span", { className: "o-labels-indicator__status floating-action-bar__live-label", children: "Live" }) }));
15
14
  }
16
- function useAlignToQanda(ref) {
17
- useEffect(() => {
18
- const align = () => {
19
- const container = ref.current;
20
- const qandaRoot = container?.closest('.qanda-container');
21
- const target = qandaRoot?.querySelector('.qanda__blocks-container');
22
- if (!target || !container)
23
- return;
24
- const rect = target.getBoundingClientRect();
25
- container.style.left = `${rect.left}px`;
26
- container.style.width = `${rect.width}px`;
27
- };
28
- align();
29
- const debounced = debounce(align, 100);
30
- window.addEventListener('resize', debounced);
31
- return () => window.removeEventListener('resize', debounced);
32
- }, [ref]);
33
- }
34
15
  function FloatingActionBar({ isLive = true }) {
35
16
  const dispatch = useDispatch();
36
17
  const isMobile = useIsMobile();
@@ -75,19 +56,17 @@ function FloatingActionBar({ isLive = true }) {
75
56
  dataTrackable: 'open_expert_drawer',
76
57
  };
77
58
  const view = showExpertView ? expertConfig : userConfig;
78
- const containerRef = useRef(null);
79
- useAlignToQanda(containerRef);
80
- return (_jsxs("div", { ref: containerRef, className: "floating-action-bar__container", "data-trackable": trackingNS('floating-action-bar'), children: [_jsx("div", { className: "floating-action-bar__fade-overlay" }), _jsxs("div", { className: "floating-action-bar", "data-testid": "floating-action-bar", children: [_jsx("span", { className: "o3-visually-hidden", "aria-live": "polite", role: "alert", children: isLive ? 'This session is now live' : '' }), isLive && _jsx(LiveIndicator, {}), _jsxs("div", { className: "floating-action-bar__buttons", ref: modalOpenerRef, children: [_jsx(Button, { attributes: {
59
+ return (_jsxs("div", { className: "floating-action-bar__container", "data-trackable": trackingNS('floating-action-bar'), children: [_jsx("div", { className: "floating-action-bar__fade-overlay" }), _jsxs("div", { className: "floating-action-bar", "data-testid": "floating-action-bar", children: [_jsx("span", { className: "o3-visually-hidden", "aria-live": "polite", role: "alert", children: isLive ? 'This session is now live' : '' }), isLive && _jsx(LiveIndicator, {}), _jsxs("div", { className: "floating-action-bar__buttons", ref: modalOpenerRef, children: [_jsx(Button, { attributes: {
81
60
  ariaLabel: view.description,
82
61
  'data-trackable': trackingNS(view.dataTrackable),
83
62
  'data-trackable-context-action': view.description,
84
63
  'data-testid': `floating-action-bar__${view.testIdSuffix}`,
85
64
  ariaHasPopup: 'dialog',
86
65
  }, label: view.description, type: "ghost", theme: "mono", icon: "edit", iconOnly: isMobile, onClick: view.onClick }), _jsx(Button, { attributes: {
87
- ariaLabel: 'Back to top',
88
- 'data-trackable': trackingNS('back_to_top'),
89
- 'data-trackable-context-trigger_action': 'Back to top',
90
- 'data-testid': 'floating-action-bar__back-to-top-button',
91
- }, label: "Back to top", type: "ghost", theme: "mono", icon: "scroll-to", iconOnly: isMobile, onClick: handleShowUpdates })] }), updatedComments?.length > 0 && (_jsx("div", { className: "floating-action-bar__updates-counter o3-type-detail", "aria-label": `${updatedComments.length} new ${updatedComments.length === 1 ? 'update' : 'updates'} available`, role: "status", "aria-live": "polite", children: updatedComments.length }))] })] }));
66
+ ariaLabel: 'Latest answers',
67
+ 'data-trackable': trackingNS('latest-answers'),
68
+ 'data-trackable-context-trigger_action': 'Latest answers',
69
+ 'data-testid': 'floating-action-bar__latest-answers-button',
70
+ }, label: "Latest answers", type: "ghost", theme: "mono", icon: "scroll-to", iconOnly: isMobile, onClick: handleShowUpdates })] }), updatedComments?.length > 0 && (_jsx("div", { className: "floating-action-bar__updates-counter o3-type-detail", "aria-label": `${updatedComments.length} new ${updatedComments.length === 1 ? 'update' : 'updates'} available`, role: "status", "aria-live": "polite", children: updatedComments.length }))] })] }));
92
71
  }
93
72
  export default FloatingActionBar;
package/dist/qanda.scss CHANGED
@@ -173,9 +173,9 @@ $app-header-color: #007acc;
173
173
  .floating-action-bar__container {
174
174
  position: fixed;
175
175
  bottom: 0;
176
- width: auto;
177
- left: auto;
178
- right: auto;
176
+ left: 0;
177
+ right: 0;
178
+ display: flex;
179
179
  /* Ensure FAB is on top of ...
180
180
  1. some dinamically loaded teasers at the bottom of the article
181
181
  2. the sharing bar on mobile
@@ -204,9 +204,8 @@ $app-header-color: #007acc;
204
204
  height: 48px;
205
205
  background: var(--o3-color-palette-white-60);
206
206
  border-radius: 100px;
207
- width: max-content;
208
- max-width: 100%;
209
- margin: 0 auto var(--o3-spacing-s);
207
+ margin: auto;
208
+ margin-bottom: var(--o3-spacing-s);
210
209
  padding: 0 var(--o3-spacing-2xs);
211
210
  display: flex;
212
211
  align-items: center;
@@ -608,33 +607,24 @@ $app-header-color: #007acc;
608
607
  }
609
608
  }
610
609
 
611
- /* Mobile will show 3 lines of text before ellipse */
612
- @container (max-width: 490px) {
613
- .qanda-block__text.expander--collapsed
614
- .expander__text--multiline
615
- > p:first-of-type {
616
- display: block;
617
- display: -webkit-box;
618
- /* Note: This need to stay aligned with the JS for the expander */
619
- line-clamp: 3;
620
- -webkit-line-clamp: 3;
621
- -webkit-box-orient: vertical;
622
- overflow: hidden;
623
- text-overflow: ellipsis;
624
- /* Without this the letter like `g` may be visually cut.
610
+ .qanda-block__text.expander--collapsed
611
+ .expander__text--multiline
612
+ > p:first-of-type {
613
+ display: block;
614
+ display: -webkit-box;
615
+ /* Note: This need to stay aligned with the JS for the expander */
616
+ line-clamp: 4;
617
+ -webkit-line-clamp: 4;
618
+ -webkit-box-orient: vertical;
619
+ overflow: hidden;
620
+ text-overflow: ellipsis;
621
+ /* Without this the letter like `g` may be visually cut.
625
622
  To be tested with final font */
626
- padding-bottom: 1px;
627
- }
628
- }
629
-
630
- /* Above mobile it will show 1 line of text before ellipse */
631
- @container (min-width: 491px) {
632
- .qanda-block__text.expander--collapsed
633
- .expander__text--multiline
634
- > p:first-of-type {
635
- white-space: nowrap;
636
- text-overflow: ellipsis;
637
- overflow: hidden;
623
+ padding-bottom: 1px;
624
+ @container (min-width: 491px) {
625
+ /* Note: This need to stay aligned with the JS for the expander */
626
+ line-clamp: 5;
627
+ -webkit-line-clamp: 5;
638
628
  }
639
629
  }
640
630
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/qanda-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Components for the Live Q&A (AKA Ask an Expert) UI",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -40,7 +40,6 @@
40
40
  },
41
41
  "homepage": "https://github.com/Financial-Times/qanda-ui#readme",
42
42
  "dependencies": {
43
- "@financial-times/o-utils": "^2.2.1",
44
43
  "@reduxjs/toolkit": "^1.9.5",
45
44
  "classnames": "^2.5.1"
46
45
  },
@@ -50,6 +49,7 @@
50
49
  "@babel/preset-env": "^7.26.0",
51
50
  "@babel/preset-react": "^7.26.3",
52
51
  "@babel/preset-typescript": "^7.26.0",
52
+ "@types/react-redux": "^7.1.34",
53
53
  "@dotcom-tool-kit/component": "^5.1.12",
54
54
  "@dotcom-tool-kit/jest": "^4.3.1",
55
55
  "@financial-times/o-colors": "^6.7.0",
@@ -59,7 +59,6 @@
59
59
  "@testing-library/preact": "^3.2.4",
60
60
  "@types/jest": "^29.5.14",
61
61
  "@types/jest-axe": "^3.5.9",
62
- "@types/react-redux": "^7.1.34",
63
62
  "@typescript-eslint/eslint-plugin": "^8.18.0",
64
63
  "@typescript-eslint/parser": "^8.18.0",
65
64
  "babel-jest": "^29.7.0",