diff --git a/coverage/base.css b/coverage/base.css
deleted file mode 100644
index f418035b..00000000
--- a/coverage/base.css
+++ /dev/null
@@ -1,224 +0,0 @@
-body, html {
- margin:0; padding: 0;
- height: 100%;
-}
-body {
- font-family: Helvetica Neue, Helvetica, Arial;
- font-size: 14px;
- color:#333;
-}
-.small { font-size: 12px; }
-*, *:after, *:before {
- -webkit-box-sizing:border-box;
- -moz-box-sizing:border-box;
- box-sizing:border-box;
- }
-h1 { font-size: 20px; margin: 0;}
-h2 { font-size: 14px; }
-pre {
- font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
- margin: 0;
- padding: 0;
- -moz-tab-size: 2;
- -o-tab-size: 2;
- tab-size: 2;
-}
-a { color:#0074D9; text-decoration:none; }
-a:hover { text-decoration:underline; }
-.strong { font-weight: bold; }
-.space-top1 { padding: 10px 0 0 0; }
-.pad2y { padding: 20px 0; }
-.pad1y { padding: 10px 0; }
-.pad2x { padding: 0 20px; }
-.pad2 { padding: 20px; }
-.pad1 { padding: 10px; }
-.space-left2 { padding-left:55px; }
-.space-right2 { padding-right:20px; }
-.center { text-align:center; }
-.clearfix { display:block; }
-.clearfix:after {
- content:'';
- display:block;
- height:0;
- clear:both;
- visibility:hidden;
- }
-.fl { float: left; }
-@media only screen and (max-width:640px) {
- .col3 { width:100%; max-width:100%; }
- .hide-mobile { display:none!important; }
-}
-
-.quiet {
- color: #7f7f7f;
- color: rgba(0,0,0,0.5);
-}
-.quiet a { opacity: 0.7; }
-
-.fraction {
- font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
- font-size: 10px;
- color: #555;
- background: #E8E8E8;
- padding: 4px 5px;
- border-radius: 3px;
- vertical-align: middle;
-}
-
-div.path a:link, div.path a:visited { color: #333; }
-table.coverage {
- border-collapse: collapse;
- margin: 10px 0 0 0;
- padding: 0;
-}
-
-table.coverage td {
- margin: 0;
- padding: 0;
- vertical-align: top;
-}
-table.coverage td.line-count {
- text-align: right;
- padding: 0 5px 0 20px;
-}
-table.coverage td.line-coverage {
- text-align: right;
- padding-right: 10px;
- min-width:20px;
-}
-
-table.coverage td span.cline-any {
- display: inline-block;
- padding: 0 5px;
- width: 100%;
-}
-.missing-if-branch {
- display: inline-block;
- margin-right: 5px;
- border-radius: 3px;
- position: relative;
- padding: 0 4px;
- background: #333;
- color: yellow;
-}
-
-.skip-if-branch {
- display: none;
- margin-right: 10px;
- position: relative;
- padding: 0 4px;
- background: #ccc;
- color: white;
-}
-.missing-if-branch .typ, .skip-if-branch .typ {
- color: inherit !important;
-}
-.coverage-summary {
- border-collapse: collapse;
- width: 100%;
-}
-.coverage-summary tr { border-bottom: 1px solid #bbb; }
-.keyline-all { border: 1px solid #ddd; }
-.coverage-summary td, .coverage-summary th { padding: 10px; }
-.coverage-summary tbody { border: 1px solid #bbb; }
-.coverage-summary td { border-right: 1px solid #bbb; }
-.coverage-summary td:last-child { border-right: none; }
-.coverage-summary th {
- text-align: left;
- font-weight: normal;
- white-space: nowrap;
-}
-.coverage-summary th.file { border-right: none !important; }
-.coverage-summary th.pct { }
-.coverage-summary th.pic,
-.coverage-summary th.abs,
-.coverage-summary td.pct,
-.coverage-summary td.abs { text-align: right; }
-.coverage-summary td.file { white-space: nowrap; }
-.coverage-summary td.pic { min-width: 120px !important; }
-.coverage-summary tfoot td { }
-
-.coverage-summary .sorter {
- height: 10px;
- width: 7px;
- display: inline-block;
- margin-left: 0.5em;
- background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
-}
-.coverage-summary .sorted .sorter {
- background-position: 0 -20px;
-}
-.coverage-summary .sorted-desc .sorter {
- background-position: 0 -10px;
-}
-.status-line { height: 10px; }
-/* yellow */
-.cbranch-no { background: yellow !important; color: #111; }
-/* dark red */
-.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
-.low .chart { border:1px solid #C21F39 }
-.highlighted,
-.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
- background: #C21F39 !important;
-}
-/* medium red */
-.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
-/* light red */
-.low, .cline-no { background:#FCE1E5 }
-/* light green */
-.high, .cline-yes { background:rgb(230,245,208) }
-/* medium green */
-.cstat-yes { background:rgb(161,215,106) }
-/* dark green */
-.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
-.high .chart { border:1px solid rgb(77,146,33) }
-/* dark yellow (gold) */
-.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
-.medium .chart { border:1px solid #f9cd0b; }
-/* light yellow */
-.medium { background: #fff4c2; }
-
-.cstat-skip { background: #ddd; color: #111; }
-.fstat-skip { background: #ddd; color: #111 !important; }
-.cbranch-skip { background: #ddd !important; color: #111; }
-
-span.cline-neutral { background: #eaeaea; }
-
-.coverage-summary td.empty {
- opacity: .5;
- padding-top: 4px;
- padding-bottom: 4px;
- line-height: 1;
- color: #888;
-}
-
-.cover-fill, .cover-empty {
- display:inline-block;
- height: 12px;
-}
-.chart {
- line-height: 0;
-}
-.cover-empty {
- background: white;
-}
-.cover-full {
- border-right: none !important;
-}
-pre.prettyprint {
- border: none !important;
- padding: 0 !important;
- margin: 0 !important;
-}
-.com { color: #999 !important; }
-.ignore-none { color: #999; font-weight: normal; }
-
-.wrapper {
- min-height: 100%;
- height: auto !important;
- height: 100%;
- margin: 0 auto -48px;
-}
-.footer, .push {
- height: 48px;
-}
diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js
deleted file mode 100644
index cc121302..00000000
--- a/coverage/block-navigation.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/* eslint-disable */
-var jumpToCode = (function init() {
- // Classes of code we would like to highlight in the file view
- var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
-
- // Elements to highlight in the file listing view
- var fileListingElements = ['td.pct.low'];
-
- // We don't want to select elements that are direct descendants of another match
- var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
-
- // Selecter that finds elements on the page to which we can jump
- var selector =
- fileListingElements.join(', ') +
- ', ' +
- notSelector +
- missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
-
- // The NodeList of matching elements
- var missingCoverageElements = document.querySelectorAll(selector);
-
- var currentIndex;
-
- function toggleClass(index) {
- missingCoverageElements
- .item(currentIndex)
- .classList.remove('highlighted');
- missingCoverageElements.item(index).classList.add('highlighted');
- }
-
- function makeCurrent(index) {
- toggleClass(index);
- currentIndex = index;
- missingCoverageElements.item(index).scrollIntoView({
- behavior: 'smooth',
- block: 'center',
- inline: 'center'
- });
- }
-
- function goToPrevious() {
- var nextIndex = 0;
- if (typeof currentIndex !== 'number' || currentIndex === 0) {
- nextIndex = missingCoverageElements.length - 1;
- } else if (missingCoverageElements.length > 1) {
- nextIndex = currentIndex - 1;
- }
-
- makeCurrent(nextIndex);
- }
-
- function goToNext() {
- var nextIndex = 0;
-
- if (
- typeof currentIndex === 'number' &&
- currentIndex < missingCoverageElements.length - 1
- ) {
- nextIndex = currentIndex + 1;
- }
-
- makeCurrent(nextIndex);
- }
-
- return function jump(event) {
- if (
- document.getElementById('fileSearch') === document.activeElement &&
- document.activeElement != null
- ) {
- // if we're currently focused on the search input, we don't want to navigate
- return;
- }
-
- switch (event.which) {
- case 78: // n
- case 74: // j
- goToNext();
- break;
- case 66: // b
- case 75: // k
- case 80: // p
- goToPrevious();
- break;
- }
- };
-})();
-window.addEventListener('keydown', jumpToCode);
diff --git a/coverage/clover.xml b/coverage/clover.xml
deleted file mode 100644
index 223cb814..00000000
--- a/coverage/clover.xml
+++ /dev/null
@@ -1,5723 +0,0 @@
-
-
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.ts | -
-
- |
- 100% | -19/19 | -100% | -3/3 | -100% | -2/2 | -100% | -19/19 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { css } from 'styled-components'; - -export const ScrollBar = css` - scrollbar-color: ${(p) => p.theme?.primary} default; - scrollbar-width: thin; - - &::-webkit-scrollbar { - width: 0.3em; - } - - &::-webkit-scrollbar-track { - box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2); - } - - &::-webkit-scrollbar-thumb { - background-color: ${(p) => p.theme?.primary}; - outline: 1px solid ${(p) => p.theme?.primary}; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 100% | -83/83 | -100% | -1/1 | -14.28% | -1/7 | -100% | -83/83 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -17x -17x -17x -17x -17x -17x -17x -17x - | import { TimelineProps } from '@models/TimelineModel'; -import { render, RenderResult } from '@testing-library/react'; -import { ReactElement } from 'react'; -import { GlobalContext } from '../../GlobalContext'; - -export const providerProps: TimelineProps = { - buttonTexts: { - dark: 'dark', - first: 'first', - last: 'last', - light: 'light', - next: 'next', - play: 'start slideshow', - previous: 'previous', - stop: 'stop slideshow', - }, - classNames: { - card: 'card', - cardMedia: 'card-media', - cardSubTitle: 'card-subtitle', - cardText: 'card-text', - cardTitle: 'card-title', - controls: 'controls', - title: 'title', - }, - darkMode: false, - enableDarkToggle: true, - fontSizes: { - cardSubtitle: '0.85rem', - cardText: '1rem', - cardTitle: '1.25rem', - title: '1.5rem', - }, - mediaHeight: 200, - mode: 'VERTICAL_ALTERNATING', - scrollable: { - scrollbar: false, - }, - showAllCardsHorizontal: false, - showProgressOnSlideshow: false, - slideItemDuration: 2000, - slideShowType: 'reveal', - textOverlay: false, - theme: { - cardBgColor: '#fff', - cardDetailsBackGround: '#ffffff', - cardDetailsColor: '#000', - cardSubtitleColor: '#000', - cardTitleColor: '#000', - detailsColor: '#000', - primary: '#0f52ba', - secondary: '#ffdf00', - titleColor: '#0f52ba', - titleColorActive: '#0f52ba', - }, - timelinePointDimension: 16, - timelinePointShape: 'circle', - titleDateFormat: 'MMM DD, YYYY', - useReadMore: true, -}; - -export const commonProps = { - disableLeft: false, - disableRight: false, - onFirst: () => {}, - onLast: () => {}, - onNext: () => {}, - onPrevious: () => {}, - onReplay: () => {}, - onToggleDarkMode: () => {}, - slideShowEnabled: false, - slideShowRunning: false, -}; - -export const customRender = ( - ui: ReactElement, - { providerProps, ...renderOptions }: any, -): RenderResult => { - return render( - <GlobalContext.Provider value={providerProps}>{ui}</GlobalContext.Provider>, - renderOptions, - ); -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.ts | -
-
- |
- 100% | -47/47 | -100% | -0/0 | -100% | -0/0 | -100% | -47/47 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; - -export const defaultTheme: Theme = { - cardBgColor: '#ffffff', - cardDetailsBackGround: '#ffffff', - cardDetailsColor: '#000', - cardMediaBgColor: '#f5f5f5', - cardSubtitleColor: '#000', - cardTitleColor: '#007FFF', - detailsColor: '#000', - iconBackgroundColor: '#007FFF', - nestedCardBgColor: '#f5f5f5', - nestedCardDetailsBackGround: '#f5f5f5', - nestedCardDetailsColor: '#000', - nestedCardSubtitleColor: '#000', - nestedCardTitleColor: '#000', - primary: '#007FFF', - secondary: '#ffdf00', - titleColor: '#007FFF', - titleColorActive: '#007FFF', - toolbarBgColor: '#f5f5f5', - toolbarBtnBgColor: '#fff', - toolbarTextColor: '#000', -}; - -export const darkTheme: Theme = { - cardBgColor: '#191919', - cardDetailsBackGround: '#191919', - cardDetailsColor: '#ffff0f', - cardMediaBgColor: '#2f2f2f', - cardSubtitleColor: '#ffffff', - cardTitleColor: '#007FFF', - detailsColor: '#ffffff', - iconBackgroundColor: '#007FFF', - nestedCardBgColor: '#333333', - nestedCardDetailsBackGround: '#333333', - nestedCardDetailsColor: '#ffffff', - nestedCardSubtitleColor: '#ffffff', - nestedCardTitleColor: '#ffffff', - primary: '#007FFF', - secondary: '#ffdf00', - titleColor: '#007FFF', - titleColorActive: '#007FFF', - toolbarBgColor: '#555', - toolbarBtnBgColor: '#222', - toolbarTextColor: '#ffffff', -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
useCloseClickOutside.ts | -
-
- |
- 93.33% | -42/45 | -100% | -6/6 | -100% | -1/1 | -93.33% | -42/45 | -
useMatchMedia.ts | -
-
- |
- 86.95% | -40/46 | -50% | -4/8 | -50% | -1/2 | -86.95% | -40/46 | -
useNewScrollPosition.ts | -
-
- |
- 32.95% | -29/88 | -100% | -2/2 | -100% | -1/1 | -32.95% | -29/88 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 | 1x -1x -29x -29x -29x -29x -29x -29x -29x -10x -10x -10x -10x -3x -3x -10x -29x -29x -29x - - - -29x -29x -29x -21x -21x -21x -21x -21x -21x -21x -29x -29x -29x -12x -12x -12x -12x -12x -12x -12x -12x -29x -29x - | import { RefObject, useCallback, useEffect, useRef } from 'react'; - -export default function useCloseClickOutside( - el: RefObject<HTMLDivElement>, - callback: () => void, -) { - const htmlElement = useRef<HTMLElement>(); - - const handleClick = useCallback((e: MouseEvent) => { - const element = htmlElement.current; - - if (element) { - if (!element.contains(e.target as Node)) { - callback(); - } - } - }, []); - - const handleEscape = useCallback((e: KeyboardEvent) => { - if (e.key === 'Escape') { - callback(); - } - }, []); - - useEffect(() => { - const element = el.current; - - if (element) { - htmlElement.current = element; - - element.addEventListener('keyup', handleEscape); - } - }, [el, callback]); - - useEffect(() => { - document.addEventListener('click', handleClick); - return () => { - const element = htmlElement.current; - if (element) { - element.removeEventListener('keyup', handleEscape); - } - document.removeEventListener('click', handleClick); - }; - }, []); -} - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -7x - - -7x -7x -7x -7x -7x -7x - - -7x -7x -7x -7x -7x -7x -10x -10x -10x -10x - - -10x -10x -10x -10x - | /** - * The useMatchMedia hook takes a media query string, a callback function, and an enabled boolean. - * It returns a boolean indicating if the media query matches the current viewport and executes the callback if it does. - * - * @param {string} query - The media query string to match against. - * @param {() => void} [cb] - Optional callback function to be executed if the media query matches. - * @param {boolean} [enabled=true] - Whether the hook is enabled or not. - * @returns {boolean} - Whether the media query matches the current viewport. - */ -import { useEffect, useState } from 'react'; - -export const useMatchMedia = ( - query: string, - cb?: () => void, - enabled = true, -) => { - const [matches, setMatches] = useState<boolean>(false); - - useEffect(() => { - if (!enabled) { - return; - } - - const media = window.matchMedia(query); - const listener = () => setMatches(media.matches); - - // Check initial match and update state if necessary - if (media.matches !== matches) { - setMatches(media.matches); - } - - media.addEventListener('change', listener); - - return () => { - media.removeEventListener('change', listener); - }; - }, [query, enabled]); - - useEffect(() => { - if (matches && cb) { - cb(); - } - }, [matches, cb]); - - return matches; -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -10x -10x -10x -10x -10x -10x -1x -1x - | import { Scroll } from '@models/TimelineHorizontalModel'; -import { TimelineMode } from '@models/TimelineModel'; -import { useMemo, useState } from 'react'; - -/** - * Hook to calculate the new scroll position based on the given mode and item width. - * - * @param {TimelineMode} mode - The mode of the timeline (HORIZONTAL, VERTICAL, or VERTICAL_ALTERNATING). - * @param {number} [itemWidth] - Optional item width for horizontal mode. - * @returns {[number, (e: HTMLElement, s: Partial<Scroll>) => void]} - The new offset and a function to compute the new offset. - */ -const useNewScrollPosition = ( - mode: TimelineMode, - itemWidth?: number, -): [number, (e: HTMLElement, s: Partial<Scroll>) => void] => { - // State to hold the new offset value - const [newOffset, setNewOffset] = useState(0); - - // Memoized function to compute the new offset value - const computeNewOffset = useMemo( - () => (parent: HTMLElement, scroll: Partial<Scroll>) => { - // Destructuring relevant properties from parent and scroll - const { clientWidth, scrollLeft, scrollTop, clientHeight } = parent; - const { pointOffset, pointWidth, contentHeight, contentOffset } = scroll; - - // Handling horizontal mode - if (mode === 'HORIZONTAL' && itemWidth && pointWidth && pointOffset) { - // Calculating right boundaries for container and circular element - const contrRight = scrollLeft + clientWidth; - const circRight = pointOffset + pointWidth; - - // Checking if the element is fully visible - const isVisible = pointOffset >= scrollLeft && circRight <= contrRight; - - // Checking if the element is partially visible - const isPartiallyVisible = - (pointOffset < scrollLeft && circRight > scrollLeft) || - (circRight > contrRight && pointOffset < contrRight); - - // Calculating gaps from left and right - const leftGap = pointOffset - scrollLeft; - const rightGap = contrRight - pointOffset; - - // Setting offset based on visibility and gap conditions - if ( - !(isVisible || isPartiallyVisible) || - (leftGap <= itemWidth && leftGap >= 0) || - (rightGap <= itemWidth && rightGap >= 0) - ) { - setNewOffset(pointOffset - itemWidth); - } - } else if (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') { - // Handling vertical modes - if (contentOffset && contentHeight) { - // Calculating bottom boundaries for container and circular element - const contrBottom = scrollTop + clientHeight; - const circBottom = contentOffset + contentHeight; - - // Checking if the element is fully visible - const isVisible = - contentOffset >= scrollTop && circBottom <= contrBottom; - - // Checking if the element is partially visible - const isPartiallyVisible = - (contentOffset < scrollTop && circBottom > scrollTop) || - (circBottom > contrBottom && contentOffset < contrBottom); - - // Calculating new offset - const nOffset = contentOffset - contentHeight; - const notVisible = !isVisible || isPartiallyVisible; - - // Setting offset based on visibility conditions - if (notVisible && nOffset + contentHeight < contrBottom) { - setNewOffset(nOffset + Math.round(contentHeight / 2)); - } else if (notVisible) { - setNewOffset(nOffset); - } - } - } - }, - [mode, itemWidth], // Dependencies for useMemo - ); - - // Returning the new offset and the function to compute it - return [newOffset, computeNewOffset]; -}; - -export default useNewScrollPosition; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
list-item.tsx | -
-
- |
- 95.16% | -59/62 | -100% | -6/6 | -50% | -1/2 | -95.16% | -59/62 | -
list.styles.ts | -
-
- |
- 100% | -109/109 | -94.11% | -16/17 | -100% | -4/4 | -100% | -109/109 | -
list.tsx | -
-
- |
- 100% | -88/88 | -100% | -12/12 | -100% | -1/1 | -100% | -88/88 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x - - - -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -6x -6x -6x -6x -6x -6x -13x -13x -13x -13x -13x -13x -13x -13x -13x -1x -1x -1x -1x -1x - | import { FunctionComponent, KeyboardEvent, memo, useCallback } from 'react'; -import { ListItemModel } from './list.model'; -import { - CheckboxStyle, - CheckboxWrapper, - ListItemStyle, - StyleAndDescription, - TitleDescriptionStyle, - TitleStyle, -} from './list.styles'; -import { CheckIcon } from 'src/components/icons'; - -const ListItem: FunctionComponent<ListItemModel> = memo( - ({ - title, - id, - description, - theme, - onClick, - active, - selected = false, - selectable = false, - }: ListItemModel) => { - const handleOnClick = useCallback((id: string) => onClick?.(id), []); - - const handleKeyPress = useCallback((ev: KeyboardEvent, id: string) => { - if (ev.key === 'Enter') { - handleOnClick(id); - } - }, []); - - return ( - <ListItemStyle - key={id} - theme={theme} - onClick={() => handleOnClick(id)} - active={active} - tabIndex={0} - selectable={selectable} - onKeyUp={(ev) => handleKeyPress(ev, id)} - > - {selectable ? ( - <CheckboxWrapper> - <CheckboxStyle role="checkbox" selected={selected} theme={theme}> - {selected && <CheckIcon />} - </CheckboxStyle> - </CheckboxWrapper> - ) : null} - <StyleAndDescription selectable={selectable}> - <TitleStyle theme={theme}>{title}</TitleStyle> - <TitleDescriptionStyle theme={theme}> - {description}{' '} - </TitleDescriptionStyle> - </StyleAndDescription> - </ListItemStyle> - ); - }, -); - -ListItem.displayName = 'ListItem'; - -export { ListItem }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme';
-import styled from 'styled-components';
-
-// Style constants
-const BACKGROUND_COLOR = '#f5f5f5';
-const BORDER_COLOR = 'rgba(0, 0, 0, 0.1)';
-
-// Common styles
-const commonStyles = `
- align-items: center;
- background: ${BACKGROUND_COLOR};
- border-radius: 4px;
- box-shadow: 0px 1px 1px ${BORDER_COLOR};
- display: flex;
- margin: 0;
- margin-bottom: 0.5rem;
- padding: 0.25rem 0.5rem;
- width: 100%;
-
- &:last-child {
- margin-bottom: 0;
- }
-`;
-
-// List styles
-export const ListStyle = styled.ul`
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- list-style: none;
- margin: 0;
- padding: 0;popo
- width: 100%;
-`;
-
-// List item styles
-export const ListItemStyle = styled.li<{
- active?: boolean;
- selectable?: boolean;
- theme: Theme;
-}>`
- ${commonStyles}
- border: ${(p) =>
- p.active ? `1px solid ${p.theme.primary}` : '1px solid transparent'};
- flex-direction: ${(p) => (p.selectable ? 'row' : 'column')};
- background: ${(p) => p.theme.toolbarBtnBgColor};
- &:hover {
- border: 1px solid ${(p) => p.theme.primary};
- cursor: pointer;
- }
- user-select: none;
-`;
-
-// Title styles
-export const TitleStyle = styled.h1<{ theme: Theme }>`
- color: ${(p) => p.theme.primary};
- font-size: 1rem;
- font-weight: normal;
- margin: 0.2rem 0;
- text-align: left;
- white-space: nowrap;
-`;
-
-// Title description styles
-export const TitleDescriptionStyle = styled.p<{ theme: Theme }>`
- font-size: 0.8rem;
- font-weight: normal;
- margin: 0;
- padding: 0.1rem;
- text-align: left;
- width: 100%;
- color: ${(p) => p.theme.cardSubtitleColor};
-`;
-
-// Checkbox wrapper styles
-export const CheckboxWrapper = styled.span`
- width: 2rem;
- display: flex;
- align-items: center;
- justify-content: center;
-`;
-
-// Checkbox styles
-export const CheckboxStyle = styled.span<{ selected?: boolean; theme: Theme }>`
- align-items: center;
- background-color: white;
- ${(p) => !p.selected && `box-shadow: inset 0 0 0 1px ${BORDER_COLOR};`}
- background: ${(p) => (p.selected ? p.theme.primary : p.theme.toolbarBgColor)};
- color: #fff;
- border-radius: 50%;
- display: flex;
- height: 1.25rem;
- justify-content: center;
- margin-right: 0.25rem;
- margin-left: 0.1rem;
- width: 1.25rem;
-
- svg {
- width: 80%;
- height: 80%;
- }
-`;
-
-// Style and description wrapper styles
-export const StyleAndDescription = styled.div<{ selectable?: boolean }>`
- flex-direction: column;
- display: flex;
- width: ${(p) => (p.selectable ? 'calc(100% - 2rem)' : '100%')};
-`;
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -8x -8x -8x -8x -8x -8x -5x -10x -10x -5x -8x -8x -8x -8x -2x -4x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -8x -8x -8x -8x -2x -2x -2x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -8x -8x -8x -8x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -8x -8x -8x -8x -1x -1x -1x - | // Import necessary dependencies and utilities -import { getUniqueID } from '@utils/index'; -import { - FunctionComponent, - startTransition, - useCallback, - useState, -} from 'react'; -import { ListItem } from './list-item'; -import { ListModel } from './list.model'; -import { ListStyle } from './list.styles'; - -// Define the List component -const List: FunctionComponent<ListModel> = ({ - items, - theme, - onClick, - activeItemIndex, - multiSelectable = false, -}) => { - // Initialize state for list items - const [listItems, setListItems] = useState(() => - items.map((item) => ({ - id: getUniqueID(), - ...item, - })), - ); - - // Callback function for handling checkbox selection - const onChecked = useCallback((id: string) => { - const updatedItems = listItems.map((item) => { - if (item.id === id) { - return { - ...item, - selected: true, - }; - } else { - return { - ...item, - selected: false, - }; - } - }); - - setListItems(updatedItems); - }, []); - - // Callback function for handling item click - const handleClick = useCallback((id: string) => { - onChecked(id); - - if (multiSelectable) { - const item = listItems.find((item) => item.id === id); - - if (item.onSelect) { - startTransition(() => { - item.onSelect(); - }); - } - } else { - onClick?.(id); - } - }, []); - - // Render the List component - return ( - <ListStyle> - {listItems?.map(({ title, id, description, selected }, index) => { - return ( - <ListItem - title={title} - id={id} - key={id} - description={description} - theme={theme} - onClick={handleClick} - selectable={multiSelectable} - selected={selected} - active={activeItemIndex === index} - /> - ); - })} - </ListStyle> - ); -}; - -// Export the List component -export { List }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 95.29% | -81/85 | -100% | -9/9 | -75% | -3/4 | -95.29% | -81/85 | -
popover.styles.ts | -
-
- |
- 100% | -94/94 | -88.23% | -15/17 | -100% | -5/5 | -100% | -94/94 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x - - - - -31x -31x -31x -31x -17x -5x -5x -5x -12x -12x -12x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -18x -31x -31x -31x -1x -1x - | import React, { FunctionComponent, useEffect, useRef, useState } from 'react'; -import useCloseClickOutside from 'src/components/effects/useCloseClickOutside'; -import { ChevronDown, CloseIcon } from 'src/components/icons'; -import { PopOverModel } from './popover.model'; -import { - CloseButton, - Content, - Header, - PopoverHolder, - PopoverWrapper, - Selecter, - SelecterIcon, - SelecterLabel, -} from './popover.styles'; - -const PopOver: FunctionComponent<PopOverModel> = ({ - children, - position, - placeholder, - theme, - width = '300px', - isDarkMode = false, -}) => { - const [open, setOpen] = useState(false); - const [isVisible, setIsVisible] = useState(false); - const ref = useRef<HTMLDivElement>(null); - - const toggleOpen = () => setOpen(!open); - - const closePopover = () => setOpen(false); - - const handleKeyPress = (ev: React.KeyboardEvent) => { - if (ev.key === 'Enter') { - toggleOpen(); - } - }; - - useCloseClickOutside(ref, closePopover); - - useEffect(() => { - if (open) { - setTimeout(() => { - setIsVisible(true); - }, 10); - } else { - setIsVisible(false); - } - }, [open]); - - return ( - <PopoverWrapper ref={ref}> - <Selecter - role="button" - onClick={toggleOpen} - theme={theme} - open={open} - isDarkMode={isDarkMode} - tabIndex={0} - onKeyUp={handleKeyPress} - > - <SelecterIcon theme={theme} open={open}> - <ChevronDown /> - </SelecterIcon> - <SelecterLabel>{placeholder}</SelecterLabel> - </Selecter> - {open ? ( - <PopoverHolder - position={position} - style={{ width: `${width}` }} - theme={theme} - visible={isVisible} - > - <Header> - <CloseButton theme={theme} onClick={closePopover}> - <CloseIcon /> - </CloseButton> - </Header> - <Content>{children}</Content> - </PopoverHolder> - ) : null} - </PopoverWrapper> - ); -}; - -export { PopOver }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -21x -15x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled from 'styled-components'; - -export const PopoverWrapper = styled.div``; - -export const PopoverHolder = styled.div<{ - position?: 'top' | 'bottom'; - theme?: Theme; - visible?: boolean; -}>` - align-items: flex-start; - background: ${({ theme }) => theme.toolbarBgColor}; - background:; - border-radius: 6px; - box-shadow: 0px 5px 16px rgba(0, 0, 0, 0.5); - display: flex; - flex-direction: column; - justify-content: space-between; - max-height: 500px; - overflow-y: auto; - padding: 0.5rem; - position: absolute; - ${(p) => (p.position === 'bottom' ? `bottom: 3.5rem` : `top: 3.5rem`)}; - width: 100%; - z-index: 100; - opacity: ${({ visible }) => (visible ? 1 : 0)}; - transition: opacity 0.1s ease-in-out; -`; - -export const Selecter = styled.div<{ - isDarkMode: boolean; - open?: boolean; - theme: Theme; -}>` - align-items: center; - background: ${({ theme }) => theme.toolbarBtnBgColor}; - color: ${({ theme }) => theme.toolbarTextColor}; - border-radius: 25px; - box-shadow: ${({ open, isDarkMode }) => - !open - ? `0px 1px 1px rgba(0, 0, 0, ${isDarkMode ? '0.85' : '0.2'})` - : 'inset 0 0 1px 1px rgba(0, 0, 0, 0.2)'}; - cursor: pointer; - display: flex; - font-weight: normal; - justify-content: space-between; - padding: 0.4rem 0.75rem 0.4rem 0.5rem; - user-select: none; -`; - -export const SelecterIcon = styled.span<{ open: boolean; theme: Theme }>` - align-items: center; - color: ${({ theme }) => theme.primary}; - display: flex; - height: 1.25rem; - justify-content: center; - width: 1.25rem; - transform: ${({ open }) => (open ? 'rotate(180deg)' : 'rotate(0deg)')}; - transition: transform 0.2s ease-in-out; - - & svg { - height: 100%; - width: 100%; - } -`; - -export const SelecterLabel = styled.span` - font-size: 0.9rem; - text-align: left; -`; - -export const Header = styled.div` - height: 30px; - width: 100%; -`; - -export const Content = styled.div` - height: calc(100% - 30px); - overflow-y: auto; - padding: 0.25rem; - width: calc(100% - 0rem); -`; - -export const CloseButton = styled.button<{ theme: Theme }>` - align-items: center; - background: transparent; - border: none; - color: ${({ theme }) => theme.primary}; - cursor: pointer; - display: flex; - justify-content: center; - margin-bottom: 0.5rem; - margin-left: auto; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 68.39% | -119/174 | -80% | -12/15 | -100% | -1/1 | -68.39% | -119/174 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -25x -25x -25x -25x -25x -25x -25x -25x -25x -25x -1x -1x -1x -24x -24x -25x -25x -4x -29x -1x -1x -1x -1x -1x -29x -24x -24x -24x -24x -24x -24x -24x -24x -24x -24x -5x -1x - -1x -1x -1x -1x -1x -1x - - - - - - - - - - - - - - - - - - -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - - - - - - - - - -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x - - -10x -10x -10x - - - - - - - - - - -10x -10x -1x - - - - -1x -1x -1x -1x -1x - | import cls from 'classnames'; -import React, { memo, useCallback, useMemo } from 'react'; -import { hexToRGBA } from '../../../utils'; -import { MaximizeIcon, MinimizeIcon, MinusIcon, PlusIcon } from '../../icons'; -import { - CardSubTitle, - CardTitle, - CardTitleAnchor, -} from '../timeline-card-content/timeline-card-content.styles'; -import { - ExpandButton, - ShowHideTextButton, -} from '../timeline-card-media/timeline-card-media-buttons'; -import { DetailsTextWrapper } from './../timeline-card-media/timeline-card-media.styles'; -import { - Content, - DetailsTextMemoModel, - ExpandButtonModel, - ShowHideTextButtonModel, - Title, -} from './memoized-model'; - -const TitleMemo = ({ - title, - url, - theme, - color, - dir, - active, - fontSize = '1rem', - classString = '', - padding = false, -}: Title) => { - return title ? ( - <CardTitle - className={cls(active ? 'active' : '', { [classString]: true })} - theme={theme} - style={{ color }} - dir={dir} - $fontSize={fontSize} - data-class={classString} - $padding={padding} - > - {url ? ( - <CardTitleAnchor href={url} target="_blank" rel="noreferrer"> - {title} - </CardTitleAnchor> - ) : ( - title - )} - </CardTitle> - ) : null; -}; - -TitleMemo.displayName = 'Timeline Title'; - -const SubTitleMemo = React.memo<Content>( - ({ content, color, dir, theme, fontSize, classString, padding }: Content) => - content ? ( - <CardSubTitle - style={{ color }} - dir={dir} - theme={theme} - $fontSize={fontSize} - className={cls('card-sub-title', classString)} - $padding={padding} - > - {content} - </CardSubTitle> - ) : null, - (prev, next) => - prev.theme?.cardSubtitleColor === next.theme?.cardSubtitleColor, -); - -SubTitleMemo.displayName = 'Timeline Content'; - -export const ExpandButtonMemo = memo<ExpandButtonModel>( - ({ theme, expanded, onExpand, textOverlay }: ExpandButtonModel) => { - const label = useMemo(() => { - return expanded ? 'Minimize' : 'Maximize'; - }, [expanded]); - - return textOverlay ? ( - <ExpandButton - onPointerDown={onExpand} - onKeyDown={(ev) => ev.key === 'Enter' && onExpand?.(ev)} - theme={theme} - aria-expanded={expanded} - tabIndex={0} - aria-label={label} - title={label} - > - {expanded ? <MinimizeIcon /> : <MaximizeIcon />} - </ExpandButton> - ) : null; - }, - (prev, next) => prev.expanded === next.expanded, -); - -ExpandButtonMemo.displayName = 'Expand Button'; - -export const ShowOrHideTextButtonMemo = memo<ShowHideTextButtonModel>( - ({ textOverlay, onToggle, theme, show }: ShowHideTextButtonModel) => { - const label = useMemo(() => { - return show ? 'Hide Text' : 'Show Text'; - }, [show]); - - return textOverlay ? ( - <ShowHideTextButton - onPointerDown={onToggle} - theme={theme} - tabIndex={0} - onKeyDown={(ev) => ev.key === 'Enter' && onToggle?.(ev)} - aria-label={label} - title={label} - > - {show ? <MinusIcon /> : <PlusIcon />} - </ShowHideTextButton> - ) : null; - }, -); - -ShowOrHideTextButtonMemo.displayName = 'Show Hide Text Button'; - -const DetailsTextMemo = memo<DetailsTextMemoModel>( - ({ - theme, - show, - expand, - textOverlay, - text, - height, - onRender, - }: DetailsTextMemoModel) => { - const onTextRef = useCallback((node: HTMLDivElement) => { - if (node) { - onRender?.(node.clientHeight); - } - }, []); - - const Text = text; - - const background = useMemo(() => { - const bg = theme?.cardDetailsBackGround || ''; - if (bg) { - return hexToRGBA(bg, 0.8); - } else { - return bg; - } - }, [theme?.cardDetailsBackGround]); - - return textOverlay ? ( - <DetailsTextWrapper - ref={onTextRef} - // height={expand ? height : 0} - $expandFull={expand} - theme={theme} - $show={show} - background={background} - > - <Text /> - </DetailsTextWrapper> - ) : null; - }, - (prev, next) => - prev.height === next.height && - prev.show === next.show && - prev.expand === next.expand && - JSON.stringify(prev.theme) === JSON.stringify(next.theme), -); - -DetailsTextMemo.displayName = 'Details Text'; - -export { TitleMemo, SubTitleMemo, DetailsTextMemo }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { keyframes } from 'styled-components'; - -export const reveal = keyframes` - 0% { - opacity: 0; - transform: scale(0.95); - } - 100% { - opacity: 1; - transform: scale(1); - } -`; - -export const slideInFromTop = keyframes` - 0% { - opacity: 0; - transform: translateY(-50%); - } - 100% { - opacity: 1; - transform: translateY(0); - } -`; - -export const slideInFromLeft = keyframes` - 0% { - opacity: 0; - transform: translateX(-50%); - } - 100% { - opacity: 1; - transform: translateX(0); - } -`; - -export const slideFromRight = keyframes` - 0% { - opacity: 0; - transform: translateX(50%); - } - 100% { - opacity: 1; - transform: translateX(0); - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -32x -32x -32x -32x -32x -32x -41x -41x -41x - - - - -41x -41x -32x -41x -41x -41x -41x -41x - - - - - - - - - - - - - - - - - -41x -41x -41x - - - - - - - - -41x -41x -41x -37x -37x -37x -37x -37x -41x -41x -41x -41x -1x -1x - | import { TimelineMode } from '@models/TimelineModel'; -import { FunctionComponent, PointerEvent, useContext, useMemo } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import ChevronIcon from '../../icons/chev-right'; -import { ContentFooterProps } from './header-footer.model'; -import { - ChevronIconWrapper, - ShowMore, - SlideShowProgressBar, - TriangleIconWrapper, -} from './timeline-card-content.styles'; - -/** - * ContentFooter - * - * A functional component that renders the footer of the timeline card. - * It displays the read more/less button, progress bar, and triangle icon. - * The read more/less button appears only if the content is large. - * The progress bar and triangle icon are displayed only if the card is in slideshow mode. - * - * @property {boolean} showProgressBar - Determines if progress bar should be displayed. - * @property {Function} onExpand - Function called when expanding content. - * @property {string} triangleDir - Direction of the triangle icon. - * @property {boolean} showMore - Determines if 'read more' should be displayed. - * @property {boolean} textContentIsLarge - Determines if text content is large. - * @property {boolean} showReadMore - Determines if 'read more' button should be displayed. - * @property {number} remainInterval - Remaining interval for progress bar. - * @property {boolean} paused - Determines if progress is paused. - * @property {number} startWidth - Starting width of progress bar. - * @property {boolean} canShow - Determines if the element can be shown. - * @property {React.RefObject} progressRef - Ref to the progress bar. - * @property {boolean} isNested - Determines if component is nested. - * @property {boolean} isResuming - Determines if slideshow is resuming. - * - * @returns {JSX.Element} ContentFooter component. - */ -const ContentFooter: FunctionComponent<ContentFooterProps> = ({ - showProgressBar, - onExpand, - triangleDir, - showMore, - textContentIsLarge, - showReadMore, - remainInterval, - paused, - startWidth, - canShow, - progressRef, - isNested, - isResuming, -}) => { - const { mode, theme } = useContext(GlobalContext); - - const canShowTriangleIcon = useMemo(() => { - return ( - !isNested && - (['VERTICAL', 'VERTICAL_ALTERNATING'] as TimelineMode[]).some( - (m) => m === mode, - ) - ); - }, [mode, isNested]); - - const handleClick = (ev: PointerEvent) => { - ev.stopPropagation(); - ev.preventDefault(); - onExpand(); - }; - - const canShowMore = useMemo(() => { - return showReadMore && textContentIsLarge; - }, [showReadMore, textContentIsLarge]); - - return ( - <> - {canShowMore ? ( - <ShowMore - className="show-more" - onPointerDown={handleClick} - onKeyUp={(event) => { - if (event.key === 'Enter') { - onExpand(); - } - }} - show={canShow ? 'true' : 'false'} - theme={theme} - tabIndex={0} - > - {<span>{showMore ? 'read less' : 'read more'}</span>} - <ChevronIconWrapper collapsed={showMore ? 'true' : 'false'}> - <ChevronIcon /> - </ChevronIconWrapper> - </ShowMore> - ) : null} - - {showProgressBar && ( - <SlideShowProgressBar - color={theme?.primary} - $duration={remainInterval} - $paused={paused} - ref={progressRef} - $startWidth={startWidth} - $resuming={isResuming} - ></SlideShowProgressBar> - )} - - {canShowTriangleIcon && ( - <TriangleIconWrapper - dir={triangleDir} - theme={theme} - offset={-8} - ></TriangleIconWrapper> - )} - </> - ); -}; - -export { ContentFooter }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -25x -25x -25x -25x -25x -25x -25x -19x -19x -19x -19x -19x -19x -19x -25x -25x -25x -19x -19x -19x -19x -19x -19x -25x -25x -25x -25x -1x -1x -1x -1x -1x -1x - | import { FunctionComponent, memo, useContext } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { SubTitleMemo, TitleMemo } from '../memoized'; -import { ContentHeaderProps } from './header-footer.model'; -import { TimelineCardHeader } from './timeline-card-content.styles'; - -/** - * ContentHeader component - * This component renders the header of the timeline card including the title and subtitle. - * It doesn't render the title and subtitle if the card has media. - * The title and subtitle are memoized to prevent unnecessary re-renders. - * - * @property {string} title - The title of the card. - * @property {string} url - The URL of the card. - * @property {boolean} media - Indicates whether the card has media or not. - * @property {string} content - The main content of the card. - * @returns {JSX.Element} The ContentHeader component. - */ -const ContentHeader: FunctionComponent<ContentHeaderProps> = memo( - ({ title, url, media, content }: ContentHeaderProps) => { - // Using context to get global values - const { fontSizes, classNames, theme } = useContext(GlobalContext); - - return ( - <TimelineCardHeader> - {/* Render title if there is no media */} - {!media && ( - <TitleMemo - title={title} - theme={theme} - url={url} - fontSize={fontSizes?.cardTitle} - classString={classNames?.cardTitle} - /> - )} - {/* Render subtitle if there is no media */} - {!media && ( - <SubTitleMemo - content={content} - theme={theme} - fontSize={fontSizes?.cardSubtitle} - classString={classNames?.cardSubTitle} - /> - )} - </TimelineCardHeader> - ); - }, -); - -// Setting display name for easier debugging -ContentHeader.displayName = 'ContentHeader'; - -export { ContentHeader }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 | 1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -1x -1x -1x -1x -1x - | import { forwardRef, useContext } from 'react';
-import { GlobalContext } from '../../GlobalContext';
-import { DetailsTextProps } from './details-text.model';
-import { getTextOrContent } from './text-or-content';
-import { TimelineContentDetailsWrapper } from './timeline-card-content.styles';
-
-const DetailsText = forwardRef<HTMLDivElement, DetailsTextProps>(
- (prop, ref) => {
- const {
- showMore,
- cardActualHeight,
- detailsHeight,
- gradientColor,
- customContent,
- timelineContent,
- detailedText,
- contentDetailsClass,
- } = prop;
-
- const {
- useReadMore,
- borderLessCards,
- contentDetailsHeight,
- textOverlay,
- theme,
- } = useContext(GlobalContext);
-
- const TextContent = getTextOrContent({
- detailedText,
- showMore,
- theme,
- timelineContent,
- });
-
- return (
- <>
- {/* detailed text */}
- <TimelineContentDetailsWrapper
- aria-expanded={showMore}
- className={contentDetailsClass}
- $customContent={!!customContent}
- ref={ref}
- theme={theme}
- $useReadMore={useReadMore}
- $borderLess={borderLessCards}
- $showMore={showMore}
- $cardHeight={!textOverlay ? cardActualHeight : null}
- $contentHeight={detailsHeight}
- height={contentDetailsHeight}
- $textOverlay={textOverlay}
- $gradientColor={gradientColor}
- >
- {customContent || (
- <TextContent
- {...{ detailedText, showMore, theme, timelineContent }}
- />
- )}
- </TimelineContentDetailsWrapper>
- </>
- );
- },
-);
-
-DetailsText.displayName = 'Details Text';
-
-export { DetailsText };
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
card-animations.styles.ts | -
-
- |
- 100% | -45/45 | -100% | -0/0 | -100% | -0/0 | -100% | -45/45 | -
content-footer.tsx | -
-
- |
- 75.21% | -88/117 | -75% | -6/8 | -33.33% | -1/3 | -75.21% | -88/117 | -
content-header.tsx | -
-
- |
- 100% | -53/53 | -100% | -7/7 | -100% | -0/0 | -100% | -53/53 | -
details-text.tsx | -
-
- |
- 100% | -66/66 | -50% | -1/2 | -100% | -0/0 | -100% | -66/66 | -
text-or-content.tsx | -
-
- |
- 90.98% | -111/122 | -45% | -9/20 | -100% | -3/3 | -90.98% | -111/122 | -
timeline-card-content.styles.ts | -
-
- |
- 78.94% | -330/418 | -67.74% | -42/62 | -89.65% | -26/29 | -78.94% | -330/418 | -
timeline-card-content.tsx | -
-
- |
- 77.48% | -358/462 | -67.21% | -41/61 | -0% | -0/1 | -77.48% | -358/462 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -2x - - - - - -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -1x -1x -1x -1x -1x -1x -1x -73x -73x -73x -73x -73x -73x -73x -41x -41x -41x -41x -41x -41x - -41x -41x -41x -1x -1x -1x -1x -1x -1x -1x -41x -40x -40x -41x -41x -41x - - - - - -41x -41x -41x -35x -35x -35x -35x -35x -35x -35x -35x -6x -41x -41x -41x -41x -41x -73x -73x -73x -73x -73x -73x -1x -1x - | import { TimelineContentModel } from '@models/TimelineContentModel'; -import { TimelineProps } from '@models/TimelineModel'; -import { - ForwardRefExoticComponent, - ReactNode, - forwardRef, - useContext, -} from 'react'; -import xss from 'xss'; -import { GlobalContext } from '../../GlobalContext'; -import { - TimelineContentDetails, - TimelineSubContent, -} from './timeline-card-content.styles'; - -// Define the type for the TextOrContentModel -export type TextOrContentModel = Pick< - TimelineContentModel, - 'timelineContent' | 'theme' | 'detailedText' -> & { - showMore?: boolean; -}; - -// Function to render an array of text -const renderTextArray: ( - p: Pick<TimelineProps, 'parseDetailsAsHTML' | 'fontSizes' | 'theme'> & { - cardTextClassName: string; - detailedText: string[]; - }, -) => ReactNode = ({ - fontSizes, - parseDetailsAsHTML, - theme, - detailedText, - cardTextClassName, -}) => { - return detailedText.map((text, index) => { - const props = parseDetailsAsHTML - ? { - dangerouslySetInnerHTML: { - __html: xss(text), - }, - } - : null; - return ( - <TimelineSubContent - key={index} - fontSize={fontSizes?.cardText} - className={cardTextClassName} - theme={theme} - {...props} - > - {parseDetailsAsHTML ? null : text} - </TimelineSubContent> - ); - }); -}; - -// Function to get the TextOrContent component -const getTextOrContent: ( - p: TextOrContentModel, -) => ForwardRefExoticComponent<TextOrContentModel> = ({ - timelineContent, - theme, - detailedText, - showMore, -}) => { - const TextOrContent = forwardRef<HTMLParagraphElement, TextOrContentModel>( - (prop, ref) => { - const isTextArray = Array.isArray(detailedText); - const { fontSizes, classNames, parseDetailsAsHTML } = - useContext(GlobalContext); - - const renderTimelineContent = () => { - if (timelineContent) { - return <div ref={ref}>{timelineContent}</div>; - } else { - let textContent = null; - if (isTextArray) { - textContent = renderTextArray({ - cardTextClassName: classNames?.cardText, - detailedText, - fontSizes, - parseDetailsAsHTML, - theme, - }); - } else { - textContent = parseDetailsAsHTML ? xss(detailedText) : detailedText; - } - - const textContentProps = - parseDetailsAsHTML && !isTextArray - ? { - dangerouslySetInnerHTML: { - __html: xss(textContent), - }, - } - : {}; - - return textContent ? ( - <TimelineContentDetails - className={showMore ? 'active' : ''} - ref={ref} - theme={theme} - {...textContentProps} - > - {parseDetailsAsHTML && !isTextArray ? null : textContent} - </TimelineContentDetails> - ) : null; - } - }; - - return renderTimelineContent(); - }, - ); - - TextOrContent.displayName = 'Text Or Content'; - - return TextOrContent; -}; - -export { getTextOrContent }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -1x -1x -1x -1x -1x -1x -1x -1x -41x - -1x -1x -1x -1x -1x -1x -1x -41x - - - - - - - - - - - - - -41x -41x -41x -41x -1x -1x -1x -41x - - -41x -41x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x - - - - - - - - - - - - - - - - - - - - - - - - -41x -1x -1x -41x - - - - - - -41x -41x - - - - -41x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -37x -1x -1x -41x -41x -41x -41x -41x -41x -41x - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -41x -41x -41x -41x -41x - - - - - - - - - -41x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - -1x -1x - - - - - - - - - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineProps } from '@models/TimelineModel'; -import styled, { css, keyframes } from 'styled-components'; -import { linearGradient } from '../timeline-card-media/timeline-card-media.styles'; -import { - reveal, - slideFromRight, - slideInFromLeft, - slideInFromTop, -} from './card-animations.styles'; - -type ContentT = Pick< - TimelineProps, - 'theme' | 'slideShow' | 'mode' | 'borderLessCards' ->; - -export const TimelineItemContentWrapper = styled.section< - { - $active?: boolean; - $borderLessCards?: TimelineProps['borderLessCards']; - $branchDir?: string; - $customContent?: boolean; - $highlight?: boolean; - $isNested?: boolean; - $maxWidth?: number; - $minHeight?: number; - $noMedia?: boolean; - $slideShow?: TimelineProps['slideShow']; - $slideShowActive?: boolean; - $slideShowType?: TimelineProps['slideShowType']; - $textOverlay?: boolean; - } & ContentT ->` - align-items: flex-start; - background: ${(p) => p.theme.cardBgColor}; - border-radius: 8px; - display: flex; - position: absolute; - ${({ borderLessCards }) => - !borderLessCards - ? `filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3))` - : 'none'}; - flex-direction: column; - justify-content: flex-start; - line-height: 1.5em; - margin: ${(p) => (p.mode === 'HORIZONTAL' ? '0 auto' : '')}; - max-width: ${(p) => p.$maxWidth}px; - // min-height: ${(p) => p.$minHeight}px; - ${(p) => - p.$customContent - ? `height: ${p.$minHeight}px;` - : `min-height: ${p.$minHeight}px;`} - position: relative; - text-align: left; - width: 98%; - z-index: 0; - - ${(p) => - p.$highlight - ? css` - &:hover { - filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.3)) brightness(1.05); - - &::before { - content: ''; - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; - z-index: -1; - border: 2px solid ${p.theme.primary}; - border-radius: 4px; - } - } - ` - : css``} - - ${(p) => - p.$isNested - ? css` - background: ${p.theme.nestedCardBgColor}; - box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1); - ` - : css``} - - height: ${(p) => (p.$textOverlay ? '0' : '')}; - - &:focus { - outline: 1px solid ${(p) => p.theme?.primary}; - } - - ${(p) => { - if (p.$slideShowActive && p.$active) { - if (p.$slideShowType === 'slide_in') { - return css` - animation: ${slideInFromTop} 0.5s ease-in-out; - `; - } else if ( - p.$slideShowType === 'slide_from_sides' && - p.$branchDir === 'left' - ) { - return css` - animation: ${slideInFromLeft} 0.5s ease-in-out; - `; - } else if ( - p.$slideShowType === 'slide_from_sides' && - p.$branchDir === 'right' - ) { - return css` - animation: ${slideFromRight} 0.5s ease-in-out; - `; - } else { - return css` - animation: ${reveal} 0.5s ease-in-out; - `; - } - } - }} - - ${(p) => { - if (p.$slideShowActive && p.$active) { - return css` - opacity: 1; - animation-timing-function: ease-in-out; - animation-duration: 0.5s; - `; - } - - if (p.$slideShowActive && !p.$active) { - return css` - opacity: 0; - `; - } - }} -`; - -export const TimelineCardHeader = styled.header` - width: 100%; - padding: 0.5rem 0.5rem 0 0.5rem; -`; - -export const CardSubTitle = styled.h2<{ - $fontSize?: string; - $padding?: boolean; - dir?: string; - theme?: Theme; -}>` - color: ${(p) => p.theme.cardSubtitleColor}; - font-size: ${(p) => p.$fontSize}; - font-weight: 600; - margin: 0; - text-align: left; - width: 97%; - padding: ${(p) => (p.$padding ? '0.5rem 0 0.5rem 0.5rem;' : '')}; -`; - -export const CardTitle = styled.h1<{ - $fontSize: string; - $padding?: boolean; - dir?: string; - theme: Theme; -}>` - color: ${(p) => p.theme.cardTitleColor}; - font-size: ${(p) => p.$fontSize}; - font-weight: 600; - margin: 0; - text-align: left; - width: 95%; - padding: ${(p) => (p.$padding ? '0.25rem 0 0.25rem 0.5rem;' : '')} &.active { - color: ${(p) => p.theme.primary}; - } -`; - -export const CardTitleAnchor = styled.a` - color: inherit; - - &:active { - color: inherit; - } -`; - -export const TimelineContentDetails = styled.p<{ theme?: Theme }>` - font-size: 0.85rem; - font-weight: 400; - margin: 0; - width: 100%; - color: ${(p) => p.theme.cardDetailsColor}; -`; - -export const TimelineSubContent = styled.span<{ - fontSize?: string; - theme?: Theme; -}>` - margin-bottom: 0.5rem; - display: block; - font-size: ${(p) => p.fontSize}; - color: ${(p) => p.theme.cardDetailsColor}; -`; - -export const TimelineContentDetailsWrapper = styled.div<{ - $borderLess?: boolean; - $cardHeight?: number | null; - $contentHeight?: number; - $customContent?: boolean; - $gradientColor?: string | null; - $showMore?: boolean; - $textOverlay?: boolean; - $useReadMore?: boolean; - branchDir?: string; - height?: number; - theme?: Theme; -}>` - align-items: center; - display: flex; - flex-direction: column; - margin: 0 auto; - margin-top: 0.5em; - margin-bottom: 0.5em; - position: relative; - ${({ $useReadMore, $customContent, $showMore, height = 0, $textOverlay }) => - $useReadMore && !$customContent && !$showMore && !$textOverlay - ? `max-height: ${height}px;` - : 'height: 100%'} - ${({ - $cardHeight = 0, - $contentHeight = 0, - height = 0, - $showMore, - $textOverlay, - }) => - $showMore && !$textOverlay - ? `max-height: ${($cardHeight || 0) + ($contentHeight || 0) - height}px;` - : ''} - overflow-x: hidden; - overflow-y: auto; - scrollbar-color: ${(p) => p.theme?.primary} default; - scrollbar-width: thin; - transition: max-height 0.25s ease-in-out; - width: ${(p) => - p.$borderLess ? 'calc(100% - 0.5rem)' : 'calc(95% - 0.5rem)'}; - padding: 0.25rem 0.25rem; - - ${(p) => (p.$customContent ? `height: 100%;` : '')} - - $${({ - height = 0, - $cardHeight = 0, - $contentHeight = 0, - $showMore, - $useReadMore, - }) => - $showMore && $useReadMore && $cardHeight - ? css` - animation: ${keyframes` - 0% { - max-height: ${height}px; - } - 100% { - max-height: ${$cardHeight + $contentHeight - height}px; - } - `} 0.25s ease-in-out; - ` - : ''} - &::-webkit-scrollbar { - width: 0.3em; - } - - &::-webkit-scrollbar-track { - box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2); - } - - &::-webkit-scrollbar-thumb { - background-color: ${(p) => p.theme?.primary}; - outline: 1px solid ${(p) => p.theme?.primary}; - } - - &.show-less { - scrollbar-width: none; - - &::-webkit-scrollbar { - width: 0; - } - overflow: hidden; - } - - --rc-gradient-color: ${(p) => p.$gradientColor}; - ${linearGradient} -`; - -export const ShowMore = styled.button<{ - show?: 'true' | 'false'; - theme?: Theme; -}>` - align-items: center; - align-self: flex-end; - border-radius: 4px; - cursor: pointer; - display: ${(p) => (p.show === 'true' ? 'flex' : 'none')}; - font-size: 0.75rem; - justify-self: flex-end; - margin-bottom: 0.5em; - margin-left: 0.5em; - margin-right: 0.5em; - margin-top: auto; - padding: 0.25em; - color: ${(p) => p.theme.primary}; - border: 0; - background: none; - - &:hover { - text-decoration: underline; - } -`; - -const slideAnimation = (start?: number, end?: number) => keyframes` - 0% { - width: ${start}px; - } - 100% { - width: ${end}px; - } -`; - -export const SlideShowProgressBar = styled.progress<{ - $color?: string; - $duration?: number; - $paused?: boolean; - $resuming?: boolean; - $startWidth?: number; -}>` - background: ${(p) => p.color}; - bottom: -0.75em; - display: block; - height: 4px; - left: 50%; - transform: translateX(-50%); - position: absolute; - border-radius: 2px; - border: 0; - - ${(p) => { - if (p.$paused) { - return css` - left: 50%; - transform: translateX(-50%); - `; - } - }} - - ${(p) => { - if (!p.$paused && p.$startWidth && p.$startWidth > 0) { - return css` - animation: ${slideAnimation(p.$startWidth, 0)} ${p.$duration}ms ease-in; - animation-play-state: running; - `; - } else { - return css` - animation-play-state: paused; - width: ${p.$startWidth}px; - `; - } - }} - - svg { - position: absolute; - left: 0; - top: 0; - width: 100%; - } -`; - -export const ChevronIconWrapper = styled.span<{ collapsed?: 'true' | 'false' }>` - align-items: center; - display: flex; - height: 1.25em; - justify-content: center; - margin-left: 0.2em; - margin-top: 0.2em; - width: 1.25em; - ${(p) => - p.collapsed === 'false' - ? ` - transform: rotate(90deg); - ` - : `transform: rotate(-90deg)`}; - - svg { - height: 100%; - width: 100%; - } -`; - -export const TriangleIconWrapper = styled.span<{ - dir?: string; - offset?: number; - theme?: Theme; -}>` - display: flex; - align-items: center; - justify-content: center; - width: 1.5rem; - height: 1.5rem; - position: absolute; - top: calc(50%); - background: ${(p) => p.theme.cardBgColor}; - transform: translateY(-50%) rotate(225deg); - z-index: -1; - - & svg { - width: 100%; - height: 100%; - fill: #fff; - } - - ${(p) => - p.dir === 'left' ? `right: ${p.offset}px;` : `left: ${p.offset}px;`}; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -32x -71x -71x -71x -71x -71x -71x -32x -71x -71x -71x -32x -32x -32x -32x -32x -71x -71x -71x -32x -6x -6x -71x -71x -71x -71x -82x -41x -41x -41x -82x - - -41x -41x -41x -41x -41x -41x -82x -71x -71x -71x -71x - - - - - - - - - - - - - - - - - - -71x -71x -71x -32x - - -71x -71x -71x -71x - - - - - - - - - - - - - -71x -71x -71x -71x - - - - - - - - - - - - - -71x -71x -71x -32x - - -32x -32x - - - -32x -32x -32x - - -32x -32x -32x -32x -71x -71x -71x -41x - - -71x -71x -71x -32x - - -71x -71x -71x -32x -71x -71x -71x -71x -71x -71x -32x -71x -71x -71x -71x -71x -71x -71x -71x - - - - - - - - - - - -71x -71x -71x -71x -71x -32x -32x -32x -32x -71x -71x -71x -71x -71x -32x -32x -28x -4x -32x -32x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -32x - -32x -32x -32x - - -71x -71x -71x - - - - -71x -71x -71x -32x - - - - - - -32x -71x -71x -71x -71x -71x -71x -71x -71x -32x -32x - -32x - -32x -71x -71x -71x -71x -32x -71x -71x -71x -32x -32x -32x -32x -32x -32x -71x -71x -71x -32x -32x -32x - - - - - - - - - - -32x -32x -32x -32x -32x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -64x -64x -64x -64x -64x -64x -64x -7x -71x -71x -71x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x - - - - - - - - - -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -1x -1x -1x -1x -1x - | import { TimelineContentModel } from '@models/TimelineContentModel'; -import { MediaState } from '@models/TimelineMediaModel'; -import { hexToRGBA } from '@utils/index'; -import cls from 'classnames'; -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import Timeline from '../../timeline/timeline'; -import CardMedia from '../timeline-card-media/timeline-card-media'; -import { ContentFooter } from './content-footer'; -import { ContentHeader } from './content-header'; -import { DetailsText } from './details-text'; -import { getTextOrContent } from './text-or-content'; -import { TimelineItemContentWrapper } from './timeline-card-content.styles'; - -const TimelineCardContent: React.FunctionComponent<TimelineContentModel> = - React.memo( - ({ - active, - content, - detailedText, - id, - media, - onShowMore, - slideShowActive, - onElapsed, - theme, - title, - onClick, - customContent, - hasFocus, - flip, - branchDir, - url, - timelineContent, - items, - isNested, - nestedCardHeight, - }: TimelineContentModel) => { - const [showMore, setShowMore] = useState(false); - const detailsRef = useRef<HTMLDivElement | null>(null); - const containerRef = useRef<HTMLDivElement | null>(null); - const progressRef = useRef<HTMLProgressElement | null>(null); - - const containerWidth = useRef<number>(0); - const slideShowElapsed = useRef(0); - const timerRef = useRef(0); - const startTime = useRef<Date>(); - const [paused, setPaused] = useState(false); - const isFirstRender = useRef(true); - - const [remainInterval, setRemainInterval] = useState(0); - const [startWidth, setStartWidth] = useState(0); - const [textContentLarge, setTextContentLarge] = useState(false); - - const [cardActualHeight, setCardActualHeight] = useState(0); - const [detailsHeight, setDetailsHeight] = useState(0); - const [hasBeenActivated, setHasBeenActivated] = useState(false); - const [isResuming, setIsResuming] = useState(false); - - const { - mode, - cardHeight, - slideItemDuration = 2000, - useReadMore, - cardWidth, - borderLessCards, - disableAutoScrollOnClick, - classNames, - textOverlay, - slideShowType, - showProgressOnSlideshow, - disableInteraction, - highlightCardsOnHover, - } = useContext(GlobalContext); - - // If the media is a video, we don't show the progress bar. - // If the media is an image, we show the progress bar if the - // showProgressOnSlideshow flag is set. - const canShowProgressBar = useMemo(() => { - return active && slideShowActive && showProgressOnSlideshow; - }, [active, slideShowActive]); - - // This function returns a boolean value that indicates whether the user - // can see more information about the item. The detailed text is only - // available if the user has expanded the row. - const canShowMore = useMemo(() => { - return !!detailedText; - }, [detailedText]); - - useEffect(() => { - const detailsEle = detailsRef.current; - - if (detailsEle) { - detailsEle.scrollTop = 0; - } - }, [showMore]); - - useEffect(() => { - if (active) { - setHasBeenActivated(true); - } - }, [active]); - - const onContainerRef = useCallback( - (node: HTMLElement) => { - if (node === null) { - return; - } - const detailsEle = detailsRef.current; - if (!detailsEle) { - return; - } - const { scrollHeight, offsetTop } = detailsEle; - containerWidth.current = node.clientWidth; - setStartWidth(containerWidth.current); - setCardActualHeight(scrollHeight); - setDetailsHeight(detailsEle.offsetHeight); - setTextContentLarge(scrollHeight + offsetTop > node.clientHeight); - }, - [detailsRef.current], - ); - - const setupTimer = useCallback((interval: number) => { - if (!slideItemDuration) { - return; - } - - setRemainInterval(interval); - - startTime.current = new Date(); - - setPaused(false); - - timerRef.current = window.setTimeout(() => { - // clear the timer and move to the next card - window.clearTimeout(timerRef.current); - setPaused(true); - setStartWidth(0); - setRemainInterval(slideItemDuration); - id && onElapsed && onElapsed(id); - }, interval); - }, []); - - useEffect(() => { - if (timerRef.current && !slideShowActive) { - window.clearTimeout(timerRef.current); - } - }, [slideShowActive]); - - // pause the slide show - const tryHandlePauseSlideshow = useCallback(() => { - if (active && slideShowActive) { - window.clearTimeout(timerRef.current); - setPaused(true); - - if (startTime.current) { - const elapsed: any = +new Date() - +startTime.current; - slideShowElapsed.current = elapsed; - } - - if (progressRef.current) { - setStartWidth(progressRef.current.clientWidth); - } - } - }, [active, slideShowActive]); - - // resumes the slide show - const tryHandleResumeSlideshow = useCallback(() => { - if (active && slideShowActive) { - if (!slideItemDuration) { - return; - } - const remainingInterval = - slideItemDuration - slideShowElapsed.current; - - setPaused(false); - - if (remainingInterval > 0) { - setupTimer(remainingInterval); - } - } - }, [active, slideShowActive, slideItemDuration]); - - useEffect(() => { - if (!slideItemDuration) { - return; - } - // setup the timer - if (active && slideShowActive) { - setStartWidth(containerWidth.current); - setupTimer(slideItemDuration); - } - - // disabled autofocus on active - if (active && hasFocus) { - containerRef.current && containerRef.current.focus(); - } - - if (!slideShowActive) { - setHasBeenActivated(false); - } - }, [active, slideShowActive]); - - useEffect(() => { - if (hasFocus && active) { - containerRef.current && containerRef.current.focus(); - } - }, [hasFocus, active]); - - useEffect(() => { - if (!paused && !isFirstRender.current) { - setIsResuming(true); - } - }, [paused, startWidth]); - - useEffect(() => { - isFirstRender.current = false; - }, []); - - // This code is used to determine whether the read more button should be shown. - // It is only shown if the useReadMore prop is true, the detailedText is non-null, - // and the customContent prop is false. - const canShowReadMore = useMemo(() => { - return useReadMore && detailedText && !customContent; - }, []); - - // decorate the comments - // This function is triggered when the media state changes. If the slideshow is - // active, and the media state changes to paused, this function will call - // tryHandlePauseSlideshow(), which will pause the slideshow. - const handleMediaState = useCallback( - (state: MediaState) => { - if (!slideShowActive) { - return; - } - if (state.playing) { - tryHandlePauseSlideshow(); - } else if (state.paused) { - if (paused && id && onElapsed) { - onElapsed(id); - } - } - }, - [paused, slideShowActive], - ); - - const contentClass = useMemo( - () => - cls( - active ? 'timeline-card-content active' : 'timeline-card-content ', - classNames?.card, - ), - [active], - ); - - const contentDetailsClass = useMemo( - () => - cls( - !showMore && !customContent && useReadMore - ? 'show-less card-description' - : 'card-description', - classNames?.cardText, - ), - [showMore, customContent], - ); - - /** - * Calculate the minimum height of the card. If the card has a text overlay and - * media, the minimum height is equal to the card height. If the card is not - * nested, the minimum height is equal to the card height. If the card is nested, - * the minimum height is equal to the nested card height. - */ - const cardMinHeight = useMemo(() => { - if (textOverlay && media) { - return cardHeight; - } else if (!isNested) { - return cardHeight; - } else { - return nestedCardHeight; - } - }, []); - - const handleExpandDetails = useCallback(() => { - if ((active && paused) || !slideShowActive) { - setShowMore(!showMore); - onShowMore(); - } - }, [active, paused, slideShowActive, showMore]); - - const triangleDir = useMemo(() => { - if (flip) { - if (branchDir === 'right') { - return 'left'; - } else { - return 'right'; - } - } - return branchDir; - }, [branchDir, flip]); - - // Get the background color for the gradient, which is either the - // cardDetailsBackGround or nestedCardDetailsBackGround theme variable, - // based on whether the card is nested or not. If we are showing more - // content, the background color should be null, so that there is no - // gradient. - const gradientColor = useMemo(() => { - const bgToUse = !isNested - ? theme?.cardBgColor - : theme?.nestedCardDetailsBackGround; - return !showMore && textContentLarge - ? hexToRGBA(bgToUse || '#ffffff', 0.8) - : null; - }, [textContentLarge, showMore, theme?.cardDetailsBackGround, isNested]); - - // This code checks whether the textOverlay and items props are truthy. If so, then it returns false. Otherwise, it returns true. - const canShowDetailsText = useMemo(() => { - return !textOverlay && !items?.length; - }, [items?.length]); - - const TextOrContent = useMemo(() => { - return getTextOrContent({ - detailedText, - showMore, - theme, - timelineContent, - }); - }, [showMore, timelineContent, theme, detailedText]); - - const handlers = useMemo(() => { - if (!isNested && !disableInteraction) { - return { - onPointerDown: (ev: React.PointerEvent) => { - ev.stopPropagation(); - if ( - !slideShowActive && - onClick && - id && - !disableAutoScrollOnClick - ) { - onClick(id); - } - }, - onPointerEnter: tryHandlePauseSlideshow, - onPointerLeave: tryHandleResumeSlideshow, - }; - } - // }, [tryHandlePauseSlideshow, tryHandleResumeSlideshow]); - }, []); - - return ( - <TimelineItemContentWrapper - className={contentClass} - $minHeight={cardMinHeight} - $maxWidth={cardWidth} - mode={mode} - $noMedia={!media} - {...handlers} - ref={onContainerRef} - tabIndex={!isNested ? 0 : -1} - theme={theme} - $borderLessCards={borderLessCards} - $textOverlay={textOverlay} - $active={hasBeenActivated} - $slideShowType={slideShowType} - $slideShowActive={slideShowActive} - $branchDir={branchDir} - $isNested={isNested} - $highlight={highlightCardsOnHover} - data-testid="timeline-card-content" - $customContent={!!customContent} - > - {title && !textOverlay ? ( - <ContentHeader - title={title} - theme={theme} - url={url} - media={media} - content={content} - /> - ) : null} - - {/* render media video or image */} - {media && ( - <CardMedia - active={active} - cardHeight={cardHeight} - content={content} - hideMedia={showMore} - id={id} - media={media} - onMediaStateChange={handleMediaState} - slideshowActive={slideShowActive} - theme={theme} - title={title} - url={url} - startWidth={startWidth} - detailsText={TextOrContent} - paused={paused} - remainInterval={remainInterval} - showProgressBar={canShowProgressBar} - triangleDir={triangleDir} - resuming={isResuming} - progressRef={progressRef} - /> - )} - - {canShowDetailsText ? ( - <DetailsText - showMore={showMore} - gradientColor={gradientColor} - detailedText={detailedText} - customContent={customContent} - timelineContent={timelineContent} - contentDetailsClass={contentDetailsClass} - cardActualHeight={cardActualHeight} - detailsHeight={detailsHeight} - ref={detailsRef} - /> - ) : ( - <Timeline - items={items} - mode={'VERTICAL'} - enableOutline={false} - hideControls - nestedCardHeight={nestedCardHeight} - isChild - /> - )} - - {(!textOverlay || !media) && ( - <ContentFooter - theme={theme} - progressRef={progressRef} - startWidth={startWidth} - textContentIsLarge={textContentLarge} - remainInterval={remainInterval} - paused={paused} - triangleDir={triangleDir} - showProgressBar={canShowProgressBar} - showReadMore={canShowReadMore} - onExpand={handleExpandDetails} - canShow={canShowMore} - showMore={showMore} - isNested={isNested} - isResuming={isResuming} - /> - )} - </TimelineItemContentWrapper> - ); - }, - ); - -TimelineCardContent.displayName = 'TimelineCardContent'; - -export default TimelineCardContent; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-card-media-buttons.tsx | -
-
- |
- 100% | -48/48 | -100% | -0/0 | -100% | -0/0 | -100% | -48/48 | -
timeline-card-media.styles.ts | -
-
- |
- 79.41% | -189/238 | -47.22% | -17/36 | -76.47% | -13/17 | -79.41% | -189/238 | -
timeline-card-media.tsx | -
-
- |
- 81.46% | -334/410 | -59.52% | -25/42 | -25% | -1/4 | -81.46% | -334/410 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled, { css } from 'styled-components'; - -const Button = css` - align-items: center; - background: none; - // background: rgba(0, 0, 0, 0.1); - border-radius: 50%; - border: none; - cursor: pointer; - display: flex; - height: 1.5rem; - justify-content: center; - padding: 0; - width: 1.5rem; - margin: 0 0.25rem; - background: ${(p) => p.theme?.primary}; - color: #fff; - - svg { - width: 70%; - height: 70%; - } -`; - -export const ExpandButton = styled.button<{ - // expandFull?: boolean; - theme: Theme; -}>` - ${Button} -`; - -export const ShowHideTextButton = styled.button<{ - showText?: boolean; - theme: Theme; -}>` - ${Button} -`; - -export const ButtonWrapper = styled.ul` - display: flex; - flex-direction: row; - justify-content: flex-end; - list-style: none; - margin: 0; - padding: 0; - margin-left: auto; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x - - - -10x -10x - - - -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x -1x -10x - - - - - - -10x -10x - - - - - - -10x -10x - - - - - - -10x -1x -1x -10x - - - - - - - - - - -10x -10x -1x -1x -1x -10x - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - - -1x -1x - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled, { css } from 'styled-components'; -import { ScrollBar } from '../../common/styles'; - -export const linearGradient = css` - &::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 2rem; - background: linear-gradient( - 0deg, - var(--rc-gradient-color) 0%, - rgba(255, 255, 255, 0) 100% - ); - } -`; - -export const MediaWrapper = styled.div<{ - $active?: boolean; - $cardHeight?: number; - $slideShowActive?: boolean; - $textOverlay?: boolean; - align?: 'left' | 'right' | 'center'; - dir?: string; - mode?: TimelineMode; - theme?: Theme; -}>` - align-items: flex-start; - align-self: center; - background: ${(p) => (!p.$textOverlay ? p.theme?.cardMediaBgColor : 'none')}; - border-radius: 4px; - flex-direction: row; - height: ${(p) => (p.$textOverlay ? 'calc(100% - 1em)' : '0')}; - padding: 0.5em; - // pointer-events: ${(p) => (!p.$active && p.$slideShowActive ? 'none' : '')}; - position: relative; - text-align: ${(p) => p.align}; - width: calc(100% - 1em); - - ${(p) => (p.$cardHeight ? `min-height: ${p.$cardHeight}px;` : '')}; - ${(p) => { - if (p.mode === 'HORIZONTAL') { - return ` - justify-content: flex-start; - `; - } else { - if (p.dir === 'left') { - return ` - justify-content: flex-start; - `; - } else { - return ` - justify-content: flex-end; - `; - } - } - }} -`; - -export const CardImage = styled.img<{ - $enableBorderRadius?: boolean; - $visible?: boolean; - dir?: string; - fit?: string; - mode?: TimelineMode; -}>` - flex: 4; - justify-self: center; - margin-left: auto; - margin-right: auto; - height: 100%; - width: 100%; - object-fit: ${(p) => p.fit || 'cover'}; - object-position: center; - visibility: ${(p) => (p.$visible ? 'visible' : 'hidden')}; - border-radius: ${(p) => (p.$enableBorderRadius ? '6px' : '0')}; -`; - -export const CardVideo = styled.video<{ height?: number }>` - max-width: 100%; - max-height: 100%; - margin-left: auto; - margin-right: auto; -`; - -export const MediaDetailsWrapper = styled.div<{ - $absolutePosition?: boolean; - $borderLessCard?: boolean; - $expandFull?: boolean; - $expandable?: boolean; - $gradientColor?: string | null; - $showText?: boolean; - $textInMedia?: boolean; - mode?: TimelineMode; - theme?: Theme; -}>` - bottom: 0; - left: 0; - right: 0; - margin-right: auto; - width: ${(p) => { - switch (p.mode) { - case 'HORIZONTAL': - case 'VERTICAL': - case 'VERTICAL_ALTERNATING': - return `calc(90% - 0rem)`; - } - }}; - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - ${(p) => { - if (p.$textInMedia && p.$expandFull) { - return css` - height: 100%; - width: 100%; - border: 0; - `; - } - - if (!p.$showText) { - return css` - height: 15%; - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); - border-radius: 10px; - `; - } - - if (p.$textInMedia && p.$expandable) { - return css` - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); - border-radius: 10px; - height: 50%; - `; - } - }} - position: ${(p) => (p.$absolutePosition ? 'absolute' : 'relative')}; - ${(p) => - p.$absolutePosition - ? ` - left: 50%; - bottom: ${p.$expandFull ? '0%' : ' 5%'}; - transform: translateX(-50%); - background: ${ - p.$showText ? p.theme?.cardDetailsBackGround : p.theme?.cardBgColor - }; - // backdrop-filter: blur(1px); - padding: 0.25rem; - ${p.$showText ? `overflow: auto;` : `overflow: hidden;`} - transition: height 0.25s ease-out, width 0.25s ease-out, bottom 0.25s ease-out, background 0.25s ease-out; - ` - : ``} - - ${({ $borderLessCard }) => - $borderLessCard - ? `border-radius: 6px; box-shadow: 0 0 10px 0 rgba(0,0,0,0.2);` - : ``} - --rc-gradient-color: ${(p) => p.$gradientColor}; - ${(p) => (p.$gradientColor ? linearGradient : null)} -`; - -export const ErrorMessage = styled.span` - color: #a3a3a3; - left: 50%; - position: absolute; - text-align: center; - top: 50%; - transform: translateY(-50%) translateX(-50%); -`; - -export const IFrameVideo = styled.iframe` - position: relative; - height: 100%; - width: 100%; -`; - -export const DetailsTextWrapper = styled.div<{ - $expandFull?: boolean; - $show?: boolean; - background: string; - theme?: Theme; -}>` - align-self: center; - display: flex; - transition: height 0.5s ease; - width: calc(100%); - background: ${(p) => p.background}; - color: ${(p) => p.theme?.cardDetailsColor}; - padding: 0.5rem; - border-bottom-left-radius: 8px; - border-bottom-right-radius: 8px; - position: relative; - align-items: flex-start; - justify-content: center; - - ${ScrollBar} - - ${(p) => { - if (p.$expandFull) { - return ` - overflow: auto; - `; - } else { - return ` - overflow: hidden; - `; - } - }} - - ${(p) => - p.$show - ? ` - height: 100%;` - : ` - height: 0; - `} - - ${(p) => !p.$expandFull && linearGradient} -`; - -export const CardMediaHeader = styled.div` - padding: 0.5rem 0 0.5rem 0.5rem; - display: flex; - align-items: center; - justify-content: center; -`; - -export const ImageWrapper = styled.div<{ height?: number }>` - width: 100%; - height: 100%; - overflow: hidden; - border-radius: 6px; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - -10x -10x -6x -6x -10x -4x -4x -4x -10x -10x -10x -10x - -10x -10x -10x -10x - - - - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - -10x -10x - - - - - -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - -10x -10x -10x -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - - - - - - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -1x - - - - -10x -10x -10x -9x -9x - - -10x -10x -10x - - - - - - - - -10x -10x -10x - - - - - - - -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x - | import { CardMediaModel } from '@models/TimelineMediaModel'; -import { hexToRGBA } from '@utils/index'; -import cls from 'classnames'; -import React, { - FunctionComponent, - memo, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { - DetailsTextMemo, - ExpandButtonMemo, - ShowOrHideTextButtonMemo, - SubTitleMemo, - TitleMemo, -} from '../memoized'; -import { - SlideShowProgressBar, - TriangleIconWrapper, -} from '../timeline-card-content/timeline-card-content.styles'; -import { ButtonWrapper } from './timeline-card-media-buttons'; -import { - CardImage, - CardMediaHeader, - CardVideo, - ErrorMessage, - IFrameVideo, - ImageWrapper, - MediaDetailsWrapper, - MediaWrapper, -} from './timeline-card-media.styles'; - -interface ErrorMessageModel { - message: string; -} - -const CardMedia: React.FunctionComponent<CardMediaModel> = ({ - active, - id, - onMediaStateChange, - title, - content, - media, - slideshowActive, - url, - detailsText, - showProgressBar, - remainInterval, - startWidth, - paused, - triangleDir, - resuming, - progressRef, -}: CardMediaModel) => { - const videoRef = useRef<HTMLVideoElement>(null); - const [loadFailed, setLoadFailed] = useState(false); - const moreRef = useRef(null); - const [detailsHeight, setDetailsHeight] = useState(0); - const [expandDetails, setExpandDetails] = useState(false); - const [showText, setShowText] = useState(true); - const [mediaLoaded, setMediaLoaded] = useState(false); - - const { - mode, - fontSizes, - classNames, - mediaHeight, - borderLessCards, - textOverlay, - theme, - cardHeight, - mediaSettings, - } = useContext(GlobalContext); - - useEffect(() => { - if (!videoRef) { - return; - } - - if (active) { - // play the video when active - videoRef.current && videoRef.current.play(); - } else { - // pause the video when not active - videoRef.current && videoRef.current.pause(); - } - }, [active]); - - // This function will be invoked when the user has finished loading media - const handleMediaLoaded = useCallback(() => { - setMediaLoaded(true); - }, []); - - // This code creates a function to handle errors when loading the video. - const handleError = useCallback(() => { - // create a function to handle errors - setLoadFailed(true); // set the loadFailed variable to true - onMediaStateChange({ - // call the onMediaStateChange function - id, - paused: false, - playing: false, - }); - }, [onMediaStateChange, id]); // add the onMediaStateChange and id variables as dependencies to the function - - const ErrorMessageMem: FunctionComponent<ErrorMessageModel> = memo( - ({ message }: ErrorMessageModel) => <ErrorMessage>{message}</ErrorMessage>, - ); - - // Checks if the media source url is a YouTube video. - // Returns a boolean. - const isYouTube = useMemo( - () => - /^(https?\:\/\/)?(www\.youtube\.com|youtu\.?be)\/.+$/.test( - media.source.url, - ), - [], - ); - - /** - * @function IFrameVideo - * @description - * The IFrameVideo component is used to display an iframe with a YouTube video. - * @returns {IFrameVideo} - Returns the iframe with the YouTube video. - */ - const IFrameYouTube = useMemo(() => { - // Create an iframe with the YouTube video - const iframe = ( - <IFrameVideo - frameBorder="0" - allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" - allowFullScreen - src={`${media.source.url}${ - active ? '?autoplay=1&enablejsapi=1' : '?enablejsapi=1' - }`} - title={media.name} - data-testid="timeline-card-content-video" - /> - ); - - // When the YouTube video is active, return the iframe - return iframe; - }, [active]); - - const Video = useMemo(() => { - return ( - <CardVideo - controls - autoPlay={active} - ref={videoRef} - onLoadedData={handleMediaLoaded} - data-testid="rc-video" - onPlay={() => - onMediaStateChange({ - id, - paused: false, - playing: true, - }) - } - onPause={() => - onMediaStateChange({ - id, - paused: true, - playing: false, - }) - } - onEnded={() => - onMediaStateChange({ - id, - paused: false, - playing: false, - }) - } - onError={handleError} - > - <source src={media.source.url}></source> - </CardVideo> - ); - }, [active]); - - const Image = useMemo(() => { - return ( - <CardImage - src={media.source.url} - mode={mode} - onLoad={handleMediaLoaded} - onError={handleError} - $visible={mediaLoaded} - alt={media.name} - loading={'lazy'} - $enableBorderRadius={borderLessCards} - fit={mediaSettings?.imageFit} - data-testid="timeline-card-content-image" - /> - ); - }, [mediaLoaded, borderLessCards]); - - ErrorMessageMem.displayName = 'Error Message'; - - // This code calculates the height of the Details component and passes it to - // the setDetailsHeight function. - const onDetailsTextRef = useCallback((height?: number) => { - if (height) { - setDetailsHeight(height); - } - }, []); - - /* Toggle the expand details state on pointer or keyboard event */ - const toggleExpand = useCallback( - (ev: React.PointerEvent | React.KeyboardEvent) => { - // ev.preventDefault(); - // ev.stopPropagation(); - setExpandDetails((prev) => !prev); - setShowText(true); - }, - [], - ); - - // This function is used to toggle the text between hidden and visible. - // It is used to show the text of the article excerpt when the user - // clicks on the "show more" button. - const toggleText = useCallback( - (ev: React.PointerEvent | React.KeyboardEvent) => { - // ev.preventDefault(); - // ev.stopPropagation(); - setExpandDetails(false); - setShowText((prev) => !prev); - }, - [], - ); - - // checks if the arrow should be shown - const canShowArrow = useMemo( - () => - (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && textOverlay, - [], - ); - - // checks if we can display the detailed text - const canShowTextMemo = useMemo( - () => showText && !!detailsText, - [showText, detailsText], - ); - - // checks if the text content should be shown - const canShowTextContent = useMemo( - () => title || content || detailsText, - [title, content, detailsText], - ); - - const canExpand = useMemo( - () => textOverlay && !!detailsText, - [content, detailsText], - ); - - const gradientColor = useMemo( - () => hexToRGBA(theme?.cardDetailsBackGround || '', 0.8), - [theme?.cardDetailsBackGround], - ); - - const canShowGradient = useMemo( - () => !expandDetails && showText && textOverlay, - [expandDetails, showText], - ); - - const getCardHeight = useMemo(() => { - if (textOverlay) { - return cardHeight; - } else { - return mediaHeight; - } - }, []); - - const TextContent = useMemo(() => { - return ( - <MediaDetailsWrapper - mode={mode} - $absolutePosition={textOverlay} - $textInMedia={textOverlay} - ref={moreRef} - theme={theme} - $expandFull={expandDetails} - $showText={showText} - $expandable={canExpand} - $gradientColor={canShowGradient ? gradientColor : null} - > - <CardMediaHeader> - <TitleMemo - title={title} - theme={theme} - active={active} - url={url} - fontSize={fontSizes?.cardTitle} - classString={classNames?.cardTitle} - /> - {canExpand ? ( - <ButtonWrapper> - <ShowOrHideTextButtonMemo - onToggle={toggleText} - show={showText} - textOverlay - theme={theme} - /> - <ExpandButtonMemo - theme={theme} - expanded={expandDetails} - onExpand={toggleExpand} - textOverlay - /> - </ButtonWrapper> - ) : null} - </CardMediaHeader> - {showText && ( - <SubTitleMemo - content={content} - fontSize={fontSizes?.cardSubtitle} - classString={classNames?.cardSubTitle} - padding - theme={theme} - /> - )} - {canShowTextMemo ? ( - <> - <DetailsTextMemo - theme={theme} - show={showText} - expand={expandDetails} - text={detailsText} - onRender={onDetailsTextRef} - textOverlay={textOverlay} - /> - </> - ) : null} - </MediaDetailsWrapper> - ); - }, [ - showText, - expandDetails, - canShowTextMemo, - gradientColor, - title, - JSON.stringify(theme), - ]); - - const canShowProgressBar = useMemo( - () => showProgressBar && textOverlay, - [showProgressBar, textOverlay], - ); - - return ( - <> - <MediaWrapper - theme={theme} - $active={active} - mode={mode} - $slideShowActive={slideshowActive} - className={cls('card-media-wrapper', classNames?.cardMedia)} - $cardHeight={getCardHeight} - align={mediaSettings?.align} - $textOverlay={textOverlay} - > - {media.type === 'VIDEO' && - !isYouTube && - (!loadFailed ? ( - Video - ) : ( - <ErrorMessageMem message="Failed to load the video" /> - ))} - {media.type === 'VIDEO' && isYouTube && IFrameYouTube} - {media.type === 'IMAGE' && - (!loadFailed ? ( - <ImageWrapper height={mediaHeight}>{Image}</ImageWrapper> - ) : ( - <ErrorMessageMem message="Failed to load the image." /> - ))} - - {canShowProgressBar ? ( - <SlideShowProgressBar - color={theme?.primary} - $duration={remainInterval} - $paused={paused} - ref={progressRef} - $startWidth={startWidth} - $resuming={resuming} - ></SlideShowProgressBar> - ) : null} - - {canShowArrow ? ( - <TriangleIconWrapper - dir={triangleDir} - theme={theme} - offset={-15} - role="img" - data-testid="arrow-icon" - ></TriangleIconWrapper> - ) : null} - </MediaWrapper> - {canShowTextContent ? TextContent : null} - </> - ); -}; - -CardMedia.displayName = 'Card Media'; - -export default CardMedia; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-horizontal-card.styles.ts | -
-
- |
- 97.33% | -146/150 | -63.63% | -14/22 | -100% | -9/9 | -97.33% | -146/150 | -
timeline-horizontal-card.tsx | -
-
- |
- 96.42% | -189/196 | -86.36% | -19/22 | -66.66% | -2/3 | -96.42% | -189/196 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -58x -58x -58x - - - - -58x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled, { keyframes } from 'styled-components'; - -export const Wrapper = styled.div` - align-items: center; - border: 1px solid transparent; - display: flex; - justify-content: center; - position: relative; - width: 100%; - height: 100%; - - &.vertical { - justify-content: flex-start; - } -`; - -export const Item = styled.div``; - -const show = keyframes` - from { - opacity: 0; - } - to { - opacity: 1; - } -`; - -export const ShapeWrapper = styled.div` - /* height: 100%; */ - align-items: center; - display: flex; - flex-direction: column; - justify-content: center; - width: 5em; -`; - -type ShapeModel = { - $timelinePointShape?: 'circle' | 'square' | 'diamond'; - dimension?: number; - theme?: Theme; -}; - -const ShapeBorderStyle = (p: Pick<ShapeModel, '$timelinePointShape'>) => { - if (p.$timelinePointShape === 'circle') { - return 'border-radius: 50%;'; - } else if (p.$timelinePointShape === 'square') { - return 'border-radius: 2px;'; - } else if (p.$timelinePointShape === 'diamond') { - return `border-radius: 0;`; - } -}; - -export const Shape = styled.div<ShapeModel>` - ${ShapeBorderStyle} - cursor: pointer; - height: ${(p) => p.dimension}px; - width: ${(p) => p.dimension}px; - transform: ${(p) => - p.$timelinePointShape === 'diamond' ? 'rotate(45deg)' : ''}; - - &.active { - &.using-icon { - /* transform: scale(1.75); */ - } - &:not(.using-icon) { - transform: ${(p) => - p.$timelinePointShape === 'diamond' ? 'rotate(45deg)' : ''}; - } - - &::after { - ${ShapeBorderStyle} - content: ''; - display: block; - height: ${(p) => (p.dimension ? Math.round(p.dimension * 0.75) : 20)}px; - width: ${(p) => (p.dimension ? Math.round(p.dimension * 0.75) : 20)}px; - left: 50%; - position: absolute; - top: 50%; - transform: translateY(-50%) translateX(-50%); - z-index: -1; - } - } - - &:not(.using-icon) { - background: ${(p: ShapeModel) => p.theme?.primary}; - - &.active { - background: ${(p: ShapeModel) => p.theme?.secondary}; - border: ${(p) => (p.dimension ? Math.round(p.dimension * 0.2) : '3')}px - solid ${(p: ShapeModel) => p.theme?.primary}; - } - - &.in-active { - } - } - - &.using-icon { - background: ${(p) => p.theme?.iconBackgroundColor}; - display: flex; - align-items: center; - justify-content: center; - - img { - max-width: 90%; - max-height: 90%; - } - } -`; - -export const TimelineTitleContainer = styled.div` - display: flex; - align-items: center; - justify-content: flex-start; - - &.vertical { - margin-bottom: 1em; - } - - &.horizontal { - position: absolute; - top: 0; - } -`; - -export const TimelineContentContainer = styled.div<{ - $active?: boolean; - $cardWidth?: number; - $highlight?: boolean; - position?: string; - theme?: Theme; -}>` - align-items: flex-start; - animation: ${show} 0.25s ease-in; - - outline: 2px solid - ${(p) => (p.$highlight && p.$active ? p.theme?.primary : 'transparent')}; - - margin: 1rem; - - &.horizontal { - min-width: ${(p) => p.$cardWidth}px; - } - - &.vertical { - width: calc(100% - 5em); - margin-left: auto; - flex-direction: column; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x - - - - -9x -9x -9x -6x -6x -6x -6x -6x -6x -6x -6x - - - -6x -6x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -6x -6x -6x -6x -6x -6x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -1x -1x - | import { TimelineCardModel } from '@models/TimelineItemModel'; -import cls from 'classnames'; -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, -} from 'react'; -import ReactDOM from 'react-dom'; -import { GlobalContext } from '../../GlobalContext'; -import TimelineCardContent from '../timeline-card-content/timeline-card-content'; -import TimelineItemTitle from '../timeline-item-title/timeline-card-title'; -import { - Shape, - ShapeWrapper, - TimelineContentContainer, - TimelineTitleContainer, - Wrapper, -} from './timeline-horizontal-card.styles'; - -const TimelineCard: React.FunctionComponent<TimelineCardModel> = ({ - active, - autoScroll, - cardDetailedText, - cardSubtitle, - cardTitle, - url, - id, - media, - onClick, - onElapsed, - slideShowRunning, - title, - wrapperId, - customContent, - hasFocus, - iconChild, - timelineContent, - cardWidth, - isNested, - nestedCardHeight, - items, -}: TimelineCardModel) => { - const circleRef = useRef<HTMLDivElement>(null); - const wrapperRef = useRef<HTMLDivElement>(null); - const contentRef = useRef<HTMLDivElement>(null); - - const { - mode, - cardPositionHorizontal: position, - timelinePointDimension, - disableClickOnCircle, - cardLess, - showAllCardsHorizontal, - classNames, - theme, - timelinePointShape, - disableInteraction, - } = useContext(GlobalContext); - - const handleClick = () => { - if (!disableClickOnCircle && onClick && !slideShowRunning) { - onClick(id); - } - }; - - useEffect(() => { - if (active) { - const circle = circleRef.current; - const wrapper = wrapperRef.current; - - if (circle && wrapper) { - const circleOffsetLeft = circle.offsetLeft; - const wrapperOffsetLeft = wrapper.offsetLeft; - - autoScroll?.({ - pointOffset: circleOffsetLeft + wrapperOffsetLeft, - pointWidth: circle.clientWidth, - }); - } - } - }, [active, autoScroll, mode]); - - const handleOnShowMore = useCallback(() => {}, []); - - const modeLower = useMemo(() => mode?.toLowerCase(), [mode]); - - const containerClass = useMemo( - () => - cls( - 'timeline-horz-card-wrapper', - modeLower, - position === 'TOP' ? 'bottom' : 'top', - showAllCardsHorizontal ? 'show-all' : '', - ), - [mode, position], - ); - - const titleClass = useMemo(() => cls(modeLower, position), []); - - const circleClass = useMemo( - () => - cls( - 'timeline-circle', - { 'using-icon': !!iconChild }, - modeLower, - active ? 'active' : 'in-active', - ), - [active], - ); - - const Content = useMemo(() => { - return ( - <TimelineContentContainer - className={containerClass} - ref={contentRef} - id={`timeline-card-${id}`} - theme={theme} - $active={active && !disableInteraction} - $highlight={showAllCardsHorizontal} - tabIndex={0} - $cardWidth={cardWidth} - > - <TimelineCardContent - content={cardSubtitle} - active={active} - title={cardTitle} - url={url} - detailedText={cardDetailedText} - onShowMore={handleOnShowMore} - theme={theme} - slideShowActive={slideShowRunning} - media={media} - onElapsed={onElapsed} - id={id} - customContent={customContent} - hasFocus={hasFocus} - onClick={onClick} - timelineContent={timelineContent} - isNested={isNested} - nestedCardHeight={nestedCardHeight} - items={items} - /> - </TimelineContentContainer> - ); - }, [active, slideShowRunning, JSON.stringify(theme)]); - - const showTimelineContent = () => { - const ele = document.getElementById(wrapperId); - - if (ele) { - return ReactDOM.createPortal(Content, ele); - } - }; - - const canShowTimelineContent = useMemo( - () => (active && !cardLess) || showAllCardsHorizontal, - [active, cardLess, showAllCardsHorizontal], - ); - - return ( - <Wrapper ref={wrapperRef} className={modeLower} data-testid="timeline-item"> - {canShowTimelineContent && showTimelineContent()} - - <ShapeWrapper> - <Shape - className={circleClass} - onClick={handleClick} - ref={circleRef} - data-testid="timeline-circle" - theme={theme} - aria-label={title} - dimension={timelinePointDimension} - $timelinePointShape={timelinePointShape} - > - {iconChild ? iconChild : null} - </Shape> - </ShapeWrapper> - - <TimelineTitleContainer - className={titleClass} - data-testid="timeline-title" - > - <TimelineItemTitle - title={title} - active={active && !disableInteraction} - theme={theme} - classString={classNames?.title} - /> - </TimelineTitleContainer> - </Wrapper> - ); -}; - -export default TimelineCard; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-control.styles.ts | -
-
- |
- 100% | -89/89 | -100% | -10/10 | -100% | -2/2 | -100% | -89/89 | -
timeline-control.tsx | -
-
- |
- 97.35% | -221/227 | -40.54% | -15/37 | -100% | -1/1 | -97.35% | -221/227 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -60x -16x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -60x -40x -40x -60x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled from 'styled-components'; - -export const TimelineNavWrapper = styled.ul<{ theme?: Theme }>` - border-radius: 25px; - display: flex; - list-style: none; - padding: 0.25em 0.25em; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); - background: ${(p) => p.theme.toolbarBtnBgColor}; -`; - -export const TimelineNavItem = styled.li<{ $disable?: boolean }>` - padding: 0.1em; - display: flex; - align-items: center; - justify-content: center; - ${(p) => - p.$disable - ? 'pointer-events: none; filter: opacity(0.5) grayscale(95%);' - : ''}; -`; - -export const TimelineNavButton = styled.button<{ - mode?: TimelineMode; - rotate?: 'TRUE' | 'FALSE'; - theme?: Theme; -}>` - align-items: center; - background: ${(p) => p.theme.primary}; - filter: brightness(1.25); - border-radius: 50%; - border: 0; - color: #fff; - cursor: pointer; - display: flex; - height: 24px; - justify-content: center; - margin: 0 0.2em; - padding: 0; - transition: all 0.1s ease-in; - width: 24px; - - transform: ${(p) => { - if (p.rotate === 'TRUE') { - return `rotate(90deg)`; - } - }}; - - &:active { - filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.25)); - transform: ${(p) => (p.rotate === 'TRUE' ? 'rotate(90deg)' : '')} scale(0.9); - } - - svg { - width: 65%; - height: 65%; - } -`; - -export const TimelineControlContainer = styled.div` - align-items: center; - display: flex; - justify-content: center; -`; - -export const ControlButton = styled.button<{ theme?: Theme }>` - align-items: center; - background: ${(p) => p.theme.primary}; - border-radius: 50%; - cursor: pointer; - display: flex; - height: 3em; - justify-content: center; - margin-left: 0.5em; - width: 3em; - outline: 0; - color: #fff; - - svg { - width: 80%; - height: 80%; - } -`; - -export const MediaToggle = styled(ControlButton)``; - -export const ReplayWrapper = styled(ControlButton)``; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -10x -10x -10x -10x -1x -1x -1x -1x - | import { TimelineControlModel } from '@models/TimelineControlModel'; -import cls from 'classnames'; -import React, { useCallback, useContext, useMemo } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { MoonIcon, StopIcon, SunIcon } from '../../icons'; -import ChevronLeft from '../../icons/chev-left'; -import ChevronRightIcon from '../../icons/chev-right'; -import ChevronsLeftIcon from '../../icons/chevs-left'; -import ChevronsRightIcon from '../../icons/chevs-right'; -import ReplayIcon from '../../icons/replay-icon'; -import { - TimelineControlContainer, - TimelineNavButton, - TimelineNavItem, - TimelineNavWrapper, -} from './timeline-control.styles'; - -/** - * TimelineControl component - * Provides navigation controls for a timeline, including next, previous, first, last, and slideshow buttons. - * Optionally supports flipping the layout and dark mode toggle. - * - * @property {function} onNext - Function to go to the next item. - * @property {function} onPrevious - Function to go to the previous item. - * @property {function} onFirst - Function to jump to the first item. - * @property {function} onLast - Function to jump to the last item. - * @property {boolean} disableLeft - Whether to disable the left navigation buttons. - * @property {boolean} disableRight - Whether to disable the right navigation buttons. - * @property {boolean} slideShowRunning - Whether the slideshow is currently running. - * @property {function} onReplay - Function to restart the slideshow. - * @property {boolean} slideShowEnabled - Whether the slideshow feature is enabled. - * @property {function} onToggleDarkMode - Function to toggle dark mode (if enabled). - * @property {boolean} isDark - Whether dark mode is currently active. - * @property {function} onPaused - Function to pause the slideshow (if running). - * @returns {JSX.Element} The TimelineControl component. - */ -const TimelineControl: React.FunctionComponent<TimelineControlModel> = ({ - onNext, - onPrevious, - onFirst, - onLast, - disableLeft, - disableRight, - slideShowRunning, - onReplay, - slideShowEnabled, - onToggleDarkMode, - isDark, - onPaused, -}: TimelineControlModel) => { - const { - mode, - flipLayout, - theme, - buttonTexts, - classNames, - enableDarkToggle, - disableInteraction, - } = useContext(GlobalContext); - - const rotate = useMemo(() => mode !== 'HORIZONTAL', [mode]); - - const flippedHorizontally = useMemo( - () => flipLayout && mode === 'HORIZONTAL', - [], - ); - - const canDisableLeft = useMemo( - () => disableLeft || slideShowRunning, - [disableLeft, slideShowRunning], - ); - - const canDisableRight = useMemo( - () => disableRight || slideShowRunning, - [disableRight, slideShowRunning], - ); - - const handlePlayOrPause = useCallback(() => { - if (slideShowRunning) { - onPaused?.(); - } else { - onReplay?.(); - } - }, [slideShowRunning]); - - const previousTitle = useMemo( - () => (flipLayout ? buttonTexts?.next : buttonTexts?.previous), - [flipLayout], - ); - - const nextTitle = useMemo( - () => (flipLayout ? buttonTexts?.previous : buttonTexts?.next), - [flipLayout], - ); - - const playOrPauseTile = useMemo( - () => (slideShowRunning ? buttonTexts?.stop : buttonTexts?.play), - [slideShowRunning], - ); - - const jumpToLastTitle = useMemo( - () => (flipLayout ? buttonTexts?.first : buttonTexts?.last), - [flipLayout], - ); - - const jumpToFirstTitle = useMemo( - () => (flipLayout ? buttonTexts?.last : buttonTexts?.first), - [flipLayout], - ); - - return ( - <TimelineControlContainer> - <TimelineNavWrapper - className={cls('timeline-controls', classNames?.controls)} - theme={theme} - > - {/* jump to first */} - {disableInteraction ? null : ( - <> - <TimelineNavItem $disable={canDisableLeft}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onLast : onFirst} - title={jumpToFirstTitle} - aria-label={jumpToFirstTitle} - aria-disabled={disableLeft} - aria-controls="timeline-main-wrapper" - tabIndex={!disableLeft ? 0 : -1} - rotate={rotate ? 'TRUE' : 'FALSE'} - > - <ChevronsLeftIcon /> - </TimelineNavButton> - </TimelineNavItem> - - {/* previous */} - <TimelineNavItem $disable={canDisableLeft}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onNext : onPrevious} - title={previousTitle} - aria-label={previousTitle} - aria-disabled={disableLeft} - aria-controls="timeline-main-wrapper" - tabIndex={!disableLeft ? 0 : -1} - rotate={rotate ? 'TRUE' : 'FALSE'} - > - <ChevronLeft /> - </TimelineNavButton> - </TimelineNavItem> - - {/* next */} - <TimelineNavItem $disable={canDisableRight}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onPrevious : onNext} - title={nextTitle} - aria-label={nextTitle} - aria-disabled={disableRight} - aria-controls="timeline-main-wrapper" - rotate={rotate ? 'TRUE' : 'FALSE'} - tabIndex={!disableRight ? 0 : -1} - > - <ChevronRightIcon /> - </TimelineNavButton> - </TimelineNavItem> - - {/* jump to last */} - <TimelineNavItem $disable={canDisableRight}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onFirst : onLast} - title={jumpToLastTitle} - aria-label={jumpToLastTitle} - aria-disabled={disableRight} - aria-controls="timeline-main-wrapper" - tabIndex={!disableRight ? 0 : -1} - rotate={rotate ? 'TRUE' : 'FALSE'} - > - <ChevronsRightIcon /> - </TimelineNavButton> - </TimelineNavItem> - </> - )} - - {/* slideshow button */} - <TimelineNavItem> - {slideShowEnabled && ( - <TimelineNavButton - theme={theme} - onClick={handlePlayOrPause} - title={playOrPauseTile} - tabIndex={0} - aria-controls="timeline-main-wrapper" - aria-label={playOrPauseTile} - > - {slideShowRunning ? <StopIcon /> : <ReplayIcon />} - </TimelineNavButton> - )} - </TimelineNavItem> - - {/* dark toggle button */} - {enableDarkToggle ? ( - <TimelineNavItem $disable={slideShowRunning}> - <TimelineNavButton - theme={theme} - onClick={onToggleDarkMode} - title={isDark ? buttonTexts?.light : buttonTexts?.dark} - tabIndex={0} - aria-controls="timeline-main-wrapper" - aria-label={isDark ? buttonTexts?.light : buttonTexts?.dark} - > - {isDark ? <SunIcon /> : <MoonIcon />} - </TimelineNavButton> - </TimelineNavItem> - ) : null} - </TimelineNavWrapper> - </TimelineControlContainer> - ); -}; - -TimelineControl.displayName = 'Timeline Control'; - -export default TimelineControl; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-card-title.styles.ts | -
-
- |
- 100% | -24/24 | -76.92% | -10/13 | -100% | -5/5 | -100% | -24/24 | -
timeline-card-title.tsx | -
-
- |
- 100% | -49/49 | -100% | -4/4 | -100% | -1/1 | -100% | -49/49 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled from 'styled-components'; - -export const TitleWrapper = styled.div<{ - $fontSize?: string; - $hide?: boolean; - align?: string; - theme?: Theme; -}>` - border-radius: 0.2rem; - font-size: ${(p) => (p.$fontSize ? p.$fontSize : '1rem')}; - font-weight: 600; - overflow: hidden; - padding: 0.25rem; - visibility: ${(p) => (p.$hide ? 'hidden' : 'visible')}; - text-align: ${(p) => (p.align ? p.align : '')}; - color: ${(p) => (p.theme ? p.theme.titleColor : '')}; - - &.active { - background: ${(p) => p.theme?.secondary}; - color: ${(p) => - p.theme?.titleColorActive ? p.theme?.titleColorActive : p.theme?.primary}; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -1x -1x - | import { TitleModel } from '@models/TimelineCardTitleModel'; -import cls from 'classnames'; -import React, { useContext, useMemo } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { TitleWrapper } from './timeline-card-title.styles'; - -/** - * TimelineItemTitle component - * This component renders the title of a timeline item and applies appropriate styling based on the given props. - * - * @property {string} title - The text of the title. - * @property {boolean} active - Indicates whether the title is active or not. - * @property {Theme} theme - The theme object, used for styling. - * @property {string} align - The alignment of the title. - * @property {string} classString - Additional CSS classes for the title. - * @returns {JSX.Element} The TimelineItemTitle component. - */ -const TimelineItemTitle: React.FunctionComponent<TitleModel> = ({ - title, - active, - theme, - align, - classString, -}: TitleModel) => { - const TITLE_CLASS = 'timeline-item-title'; // Base class name for the title - - // Computed class name for the title, combining base class, active state, and additional classes - const titleClass = useMemo( - () => cls(TITLE_CLASS, active ? 'active' : '', classString), - [active, classString], - ); - - // Get font size from global context - const { fontSizes } = useContext(GlobalContext); - - return ( - <TitleWrapper - className={titleClass} - theme={theme} - $hide={!title} - align={align} - $fontSize={fontSizes?.title} - > - {title} - </TitleWrapper> - ); -}; - -export default TimelineItemTitle; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-horizontal.styles.ts | -
-
- |
- 100% | -35/35 | -66.66% | -2/3 | -100% | -0/0 | -100% | -35/35 | -
timeline-horizontal.tsx | -
-
- |
- 100% | -103/103 | -50% | -3/6 | -100% | -1/1 | -100% | -103/103 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import styled from 'styled-components';
-
-export const TimelineHorizontalWrapper = styled.ul<{ flipLayout?: boolean }>`
- display: flex;
- list-style: none;
- margin: 0;
- width: 100%;
- direction: ${(p) => (p.flipLayout ? 'rtl' : 'ltr')};
-
- &.vertical {
- flex-direction: column;
- }
- &.horizontal {
- flex-direction: row;
- }
-`;
-
-export const TimelineItemWrapper = styled.li<{ width: number }>`
- width: ${(p) => p.width}px;
- visibility: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- height: 150px;
- flex-direction: column;
-
- &.vertical {
- margin-bottom: 2rem;
- width: 100%;
- }
-
- &.visible {
- visibility: visible;
- }
-`;
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -1x -1x -1x -1x -1x -1x - | import { TimelineHorizontalModel } from '@models/TimelineHorizontalModel'; -import cls from 'classnames'; -import React, { ReactNode, useContext, useMemo } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import TimelineCard from '../timeline-elements/timeline-card/timeline-horizontal-card'; -import { - TimelineHorizontalWrapper, - TimelineItemWrapper, -} from './timeline-horizontal.styles'; - -/** - * TimelineHorizontal - * @property {TimelineHorizontalModel} items - The items to be displayed in the timeline. - * @property {(item: TimelineItem) => void} handleItemClick - Function to handle item click. - * @property {boolean} autoScroll - Whether to auto-scroll the timeline. - * @property {string} wrapperId - The ID of the wrapper element. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {() => void} onElapsed - Function to handle elapsed time. - * @property {React.ReactNode} contentDetailsChildren - The children nodes for content details. - * @property {boolean} hasFocus - Whether the timeline has focus. - * @property {React.ReactNode} iconChildren - The children nodes for icons. - * @property {number} nestedCardHeight - The height of the nested card. - * @property {boolean} isNested - Whether the card is nested. - * @returns {JSX.Element} The TimelineHorizontal component. - */ - -const TimelineHorizontal: React.FunctionComponent<TimelineHorizontalModel> = ({ - items, - handleItemClick, - autoScroll, - wrapperId, - slideShowRunning, - onElapsed, - contentDetailsChildren: children, - hasFocus, - iconChildren, - nestedCardHeight, - isNested, -}: TimelineHorizontalModel) => { - const { - mode = 'HORIZONTAL', - itemWidth = 200, - cardHeight, - flipLayout, - showAllCardsHorizontal, - theme, - cardWidth, - } = useContext(GlobalContext); - - console.log('showing', showAllCardsHorizontal); - - // Memoize the wrapper class to avoid unnecessary re-renders - const wrapperClass = useMemo( - () => - cls( - mode.toLowerCase(), - 'timeline-horizontal-container', - showAllCardsHorizontal ? 'show-all-cards-horizontal' : '', - ), - [mode, showAllCardsHorizontal], - ); - - const iconChildColln = React.Children.toArray(iconChildren); - - return ( - <TimelineHorizontalWrapper - className={wrapperClass} - flipLayout={flipLayout} - data-testid="timeline-collection" - > - {items.map((item, index) => ( - <TimelineItemWrapper - key={item.id} - width={itemWidth} - className={cls( - item.visible ? 'visible' : '', - 'timeline-horz-item-container', - )} - > - <TimelineCard - {...item} - onClick={handleItemClick} - autoScroll={autoScroll} - wrapperId={wrapperId} - theme={theme} - slideShowRunning={slideShowRunning} - cardHeight={cardHeight} - onElapsed={onElapsed} - customContent={children ? (children as ReactNode[])[index] : null} - hasFocus={hasFocus} - iconChild={iconChildColln[index]} - active={item.active} - cardWidth={cardWidth} - isNested={isNested} - nestedCardHeight={nestedCardHeight} - /> - </TimelineItemWrapper> - ))} - </TimelineHorizontalWrapper> - ); -}; - -export default TimelineHorizontal; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-point.tsx | -
-
- |
- 92.64% | -126/136 | -63.63% | -7/11 | -0% | -0/1 | -92.64% | -126/136 | -
timeline-vertical-item.tsx | -
-
- |
- 96.17% | -226/235 | -43.75% | -7/16 | -100% | -1/1 | -96.17% | -226/235 | -
timeline-vertical-shape.styles.ts | -
-
- |
- 100% | -53/53 | -62.5% | -5/8 | -100% | -3/3 | -100% | -53/53 | -
timeline-vertical.styles.ts | -
-
- |
- 85.41% | -123/144 | -57.14% | -12/21 | -100% | -5/5 | -85.41% | -123/144 | -
timeline-vertical.tsx | -
-
- |
- 93.04% | -107/115 | -80% | -8/10 | -100% | -1/1 | -93.04% | -107/115 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x - -20x -20x -20x -20x -20x -20x -20x -20x - - - - -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x - - - - - -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -1x -1x -1x -1x -1x -1x - | import { TimelinePointModel } from '@models/TimelineVerticalModel'; -import cls from 'classnames'; -import React, { memo, useContext, useEffect, useMemo, useRef } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import { Shape } from '../timeline-elements/timeline-card/timeline-horizontal-card.styles'; -import { - TimelinePointContainer, - TimelinePointWrapper, -} from './timeline-vertical-shape.styles'; - -/** - * TimelinePoint - * @property {string} className - The class name for the component. - * @property {string} id - The id of the timeline point. - * @property {() => void} onClick - Function to handle click event. - * @property {boolean} active - Whether the timeline point is active. - * @property {() => void} onActive - Function to handle active event. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {React.ReactNode} iconChild - The icon child nodes. - * @property {number} timelinePointDimension - The dimension of the timeline point. - * @property {number} lineWidth - The width of the line. - * @property {boolean} disableClickOnCircle - Whether the click on circle is disabled. - * @property {boolean} cardLess - Whether the card is less. - * @returns {JSX.Element} The TimelinePoint component. - */ -const TimelinePoint: React.FunctionComponent<TimelinePointModel> = memo( - (props: TimelinePointModel) => { - const { - className, - id, - onClick, - active, - onActive, - slideShowRunning, - iconChild, - timelinePointDimension, - lineWidth, - disableClickOnCircle, - cardLess, - } = props; - - const circleRef = useRef<HTMLButtonElement>(null); - const { - theme, - focusActiveItemOnLoad, - timelinePointShape, - disableTimelinePoint, - } = useContext(GlobalContext); - - const isFirstRender = useRef(true); - - // Determine if onActive can be invoked - const canInvokeOnActive = useMemo(() => { - if (focusActiveItemOnLoad) { - return active; - } else { - return active && !isFirstRender.current; - } - }, [active]); - - // Invoke onActive if conditions are met - useEffect(() => { - if (canInvokeOnActive) { - const circle = circleRef.current; - - circle && onActive(circle.offsetTop); - } - }, [canInvokeOnActive, active]); - - // Determine circle class - const circleClass = useMemo( - () => - cls({ - active, - 'using-icon': !!iconChild, - }), - [active, iconChild], - ); - - // Determine click handler props - const clickHandlerProps = useMemo( - () => - !disableClickOnCircle && { - onClick: (ev: React.MouseEvent) => { - ev.stopPropagation(); - if (id && onClick && !slideShowRunning) { - onClick(id); - } - }, - }, - [id, onClick, slideShowRunning, disableClickOnCircle], - ); - - // Update isFirstRender flag after first render - useEffect(() => { - if (isFirstRender.current) { - isFirstRender.current = false; - } - }, []); - - return ( - <TimelinePointWrapper - width={lineWidth} - bg={theme && theme.primary} - className={className} - data-testid="tree-leaf" - $cardLess={cardLess} - > - {/* {!disableTimelinePoint ? ( */} - <TimelinePointContainer - className={`${className} timeline-vertical-circle`} - {...clickHandlerProps} - ref={circleRef} - data-testid="tree-leaf-click" - aria-label="select timeline" - $hide={disableTimelinePoint} - > - <Shape - className={circleClass} - theme={theme} - dimension={timelinePointDimension} - $timelinePointShape={timelinePointShape} - > - {iconChild ? iconChild : null} - </Shape> - </TimelinePointContainer> - {/* ) : null} */} - </TimelinePointWrapper> - ); - }, - (prev, next) => prev.active === next.active, -); - -TimelinePoint.displayName = 'TimelinePoint'; - -export { TimelinePoint }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x - - - - - -29x -29x -29x -29x -29x - - - -29x -29x -29x -29x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -20x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x - -29x -29x -29x -29x -29x -1x -1x -1x -1x - | import { VerticalItemModel } from '@models/TimelineVerticalModel'; -import cls from 'classnames'; -import React, { useCallback, useContext, useMemo, useRef } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import TimelineCard from '../timeline-elements/timeline-card-content/timeline-card-content'; -import TimelineItemTitle from '../timeline-elements/timeline-item-title/timeline-card-title'; -import { TimelinePoint } from './timeline-point'; -import { - TimelineCardContentWrapper, - TimelineTitleWrapper, - VerticalItemWrapper, -} from './timeline-vertical.styles'; - -/** - * VerticalItem - * @property {boolean} active - Whether the vertical item is active. - * @property {boolean} alternateCards - Whether to alternate cards. - * @property {string} cardDetailedText - The detailed text of the card. - * @property {string} cardSubtitle - The subtitle of the card. - * @property {string} cardTitle - The title of the card. - * @property {string} url - The URL of the card. - * @property {string} className - The class name for the component. - * @property {React.ReactNode} contentDetailsChildren - The content details children nodes. - * @property {React.ReactNode} iconChild - The icon child nodes. - * @property {boolean} hasFocus - Whether the vertical item has focus. - * @property {string} id - The id of the vertical item. - * @property {React.ReactNode} media - The media nodes. - * @property {() => void} onActive - Function to handle active event. - * @property {() => void} onClick - Function to handle click event. - * @property {() => void} onElapsed - Function to handle elapsed event. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {string} title - The title of the vertical item. - * @property {boolean} visible - Whether the vertical item is visible. - * @property {React.ReactNode} timelineContent - The timeline content nodes. - * @property {Array} items - The items of the vertical item. - * @property {boolean} isNested - Whether the vertical item is nested. - * @property {number} nestedCardHeight - The height of the nested card. - * @returns {JSX.Element} The VerticalItem component. - */ -const VerticalItem: React.FunctionComponent<VerticalItemModel> = ( - props: VerticalItemModel, -) => { - const contentRef = useRef<HTMLLIElement>(null); - - const { - active, - alternateCards, - cardDetailedText, - cardSubtitle, - cardTitle, - url, - className, - contentDetailsChildren, - iconChild, - hasFocus, - id, - media, - onActive, - onClick, - onElapsed, - slideShowRunning, - title, - visible, - timelineContent, - items, - isNested, - nestedCardHeight, - } = props; - - const { - cardHeight, - mode, - flipLayout, - timelinePointDimension, - lineWidth, - disableClickOnCircle, - cardLess, - theme, - classNames, - textOverlay, - mediaHeight, - disableInteraction, - } = useContext(GlobalContext); - - // handler for onActive - const handleOnActive = useCallback( - (offset: number) => { - if (contentRef.current) { - const { offsetTop, clientHeight } = contentRef.current; - onActive(offsetTop + offset, offsetTop, clientHeight); - } - }, - [onActive], - ); - - // handler for read more - const handleShowMore = useCallback(() => { - setTimeout(() => { - handleOnActive(0); - }, 100); - }, [handleOnActive]); - - // timeline title - const Title = useMemo(() => { - return ( - <TimelineTitleWrapper - className={className} - $alternateCards={alternateCards} - mode={mode} - $hide={!title} - $flip={!alternateCards && flipLayout} - > - <TimelineItemTitle - title={title} - active={active && !disableInteraction} - theme={theme} - align={flipLayout ? 'left' : 'right'} - classString={classNames?.title} - /> - </TimelineTitleWrapper> - ); - }, [ - active, - title, - className, - alternateCards, - mode, - flipLayout, - theme, - classNames, - ]); - - const verticalItemClass = useMemo( - () => - cls({ [className]: true }, 'vertical-item-row', visible ? 'visible' : ''), - [className, visible], - ); - - const contentClass = cls('card-content-wrapper', visible ? 'visible' : '', { - [className]: true, - }); - - // timeline circle - const TimelinePointMemo = useMemo( - () => ( - <TimelinePoint - active={active} - alternateCards={alternateCards} - className={className} - id={id} - mode={mode} - onActive={handleOnActive} - onClick={onClick} - slideShowRunning={slideShowRunning} - iconChild={iconChild} - timelinePointDimension={timelinePointDimension} - lineWidth={lineWidth} - disableClickOnCircle={disableClickOnCircle} - cardLess={cardLess} - /> - ), - [ - slideShowRunning, - active, - alternateCards, - className, - id, - mode, - handleOnActive, - onClick, - iconChild, - timelinePointDimension, - lineWidth, - disableClickOnCircle, - cardLess, - ], - ); - - return ( - <VerticalItemWrapper - $alternateCards={alternateCards} - $cardHeight={isNested ? nestedCardHeight : cardHeight} - className={verticalItemClass} - data-testid="vertical-item-row" - key={id} - ref={contentRef} - $cardLess={cardLess} - $isNested={isNested} - theme={theme} - > - {/* title */} - {!isNested ? Title : null} - - {/* card section */} - <TimelineCardContentWrapper - className={contentClass} - $alternateCards={alternateCards} - $noTitle={!title} - $flip={!alternateCards && flipLayout} - height={textOverlay ? mediaHeight : cardHeight} - > - {!cardLess ? ( - // <span></span> - <TimelineCard - active={active} - branchDir={className} - content={cardSubtitle} - customContent={contentDetailsChildren} - detailedText={cardDetailedText} - hasFocus={hasFocus} - id={id} - media={media} - onClick={onClick} - onElapsed={onElapsed} - onShowMore={handleShowMore} - slideShowActive={slideShowRunning} - theme={theme} - title={cardTitle} - url={url} - flip={!alternateCards && flipLayout} - timelineContent={timelineContent} - items={items} - isNested={isNested} - nestedCardHeight={nestedCardHeight} - /> - ) : null} - </TimelineCardContentWrapper> - {!isNested ? TimelinePointMemo : null} - </VerticalItemWrapper> - ); -}; - -VerticalItem.displayName = 'VerticalItem'; - -export default VerticalItem; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import styled from 'styled-components'; - -export const TimelinePointWrapper = styled.div<{ - $cardLess?: boolean; - bg?: string; - width?: number; -}>` - align-items: center; - display: flex; - justify-content: center; - position: relative; - width: 5%; - - &.left { - order: 2; - } - - &.right { - order: 1; - } - - &::before { - background: ${(p) => p.bg}; - width: ${(p) => (p.width ? `${p.width}px` : '4px')}; - height: 2rem; - position: absolute; - content: ''; - display: block; - left: 50%; - top: -1rem; - transform: translateY(-50%) translateX(-50%); - } - - &::after { - background: ${(p) => p.bg}; - content: ''; - display: block; - height: 100%; - left: 50%; - position: absolute; - width: ${(p) => (p.width ? `${p.width}px` : '4px')}; - z-index: 0; - transform: translateX(-50%); - } -`; - -export const TimelinePointContainer = styled.button<{ $hide?: boolean }>` - position: relative; - z-index: 1; - visibility: ${(p) => (p.$hide ? 'hidden' : 'visible')}; - background: none; - border: 0; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x - - - - - - - - - -29x -29x -29x -29x -29x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -21x -29x - -8x -8x -8x -29x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x - - - - - - - - - - -29x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -20x - -20x -20x -20x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled, { css, keyframes } from 'styled-components'; - -export const TimelineVerticalWrapper = styled.div` - display: flex; - flex-direction: column; - width: 100%; - padding: 1em; - outline: 0; -`; - -const animateVisible = keyframes` - from { - opacity: 0; - visibility: hidden; - } - to { - opacity: 1; - visibility: visible; - } -`; - -export const VerticalItemWrapper = styled.li<{ - $alternateCards?: boolean; - $cardHeight?: number; - $cardLess?: boolean; - $isNested?: boolean; - theme?: Theme; -}>` - display: flex; - position: relative; - visibility: hidden; - width: 100%; - align-items: stretch; - justify-content: center; - margin: 1rem 0; - list-style: none; - - &.left { - margin-right: auto; - } - &.right { - margin-left: auto; - } - - &.visible { - visibility: visible; - } - - ${(p) => - p.$isNested - ? css` - position: relative; - - &:not(:last-child)::after { - content: ''; - position: absolute; - width: 2px; - height: 2rem; - background: ${(p) => p.theme.primary}; - left: 50%; - transform: translateX(-50%); - bottom: -2rem; - } - ` - : css``} -`; - -export const TimelineCardContentWrapper = styled.div<{ - $alternateCards?: boolean; - $cardLess?: boolean; - $flip?: boolean; - $noTitle?: boolean; - height?: number; -}>` - visibility: hidden; - position: relative; - display: flex; - align-items: center; - ${(p) => { - if (p.$alternateCards) { - return `width: 50%;`; - } else if (p.$noTitle) { - return `width: 95%;`; - } else { - return `width: 85%;`; - } - }} - ${(p) => { - if (!p.$flip) { - return ` - &.left { - order: 1; - justify-content: flex-end; - } - &.right { - order: 3; - justify-content: flex-start; - } - `; - } else { - return ` - justify-content: flex-end; - &.left { - order: 3; - } - &.right { - order: 1; - } - `; - } - }} - &.visible { - visibility: visible; - animation: ${animateVisible} 0.25s ease-in; - } -`; - -export const TimelineTitleWrapper = styled.div<{ - $alternateCards?: boolean; - $flip?: boolean; - $hide?: boolean; - mode?: TimelineMode; -}>` - align-items: center; - display: ${(p) => (p.$hide && p.mode === 'VERTICAL' ? 'none' : 'flex')}; - ${(p) => (p.$alternateCards ? 'width: 50%' : 'width: 10%')}; - - &.left { - justify-content: ${(p) => (p.$flip ? 'flex-end' : 'flex-start')}; - order: ${(p) => (p.$flip && p.mode === 'VERTICAL_ALTERNATING' ? '1' : '3')}; - } - - &.right { - ${(p) => - p.$flip - ? ` - order: 3; - justify-content: flex-start;` - : `order: 1; - justify-content: flex-end;`}; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x - - - - - - -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -27x -27x -27x -27x -21x -27x -6x -6x -27x -27x -27x - -27x -27x -27x - -27x -9x -18x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -9x -9x -9x -9x -1x -1x -1x -1x - | import { TimelineVerticalModel } from '@models/TimelineVerticalModel'; -import React, { useCallback } from 'react'; -import TimelineVerticalItem from './timeline-vertical-item'; -import { TimelineVerticalWrapper } from './timeline-vertical.styles'; - -/** - * TimelineVertical - * @property {boolean} alternateCards - Whether to alternate cards. - * @property {() => void} autoScroll - Function to handle auto scroll. - * @property {React.ReactNode} contentDetailsChildren - The content details children nodes. - * @property {boolean} enableOutline - Whether to enable outline. - * @property {boolean} hasFocus - Whether the timeline has focus. - * @property {React.ReactNode} iconChildren - The icon children nodes. - * @property {Array} items - The items of the timeline. - * @property {string} mode - The mode of the timeline. - * @property {() => void} onClick - Function to handle click event. - * @property {() => void} onElapsed - Function to handle elapsed event. - * @property {() => void} onOutlineSelection - Function to handle outline selection. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {Object} theme - The theme of the timeline. - * @property {boolean} cardLess - Whether the card is less. - * @property {number} nestedCardHeight - The height of the nested card. - * @returns {JSX.Element} The TimelineVertical component. - */ -const TimelineVertical: React.FunctionComponent<TimelineVerticalModel> = ({ - alternateCards = true, - autoScroll, - contentDetailsChildren, - enableOutline, - hasFocus, - iconChildren, - items, - mode, - onClick, - onElapsed, - onOutlineSelection, - slideShowRunning, - theme, - cardLess, - nestedCardHeight, -}: TimelineVerticalModel) => { - // check if the timeline that has become active is visible. - // if not auto scroll the content and bring it to the view. - const handleOnActive = useCallback( - (offset: number, wrapperOffset: number, height: number) => { - autoScroll({ - contentHeight: height, - contentOffset: wrapperOffset, - pointOffset: offset, - }); - }, - [autoScroll], - ); - - // todo remove this - const handleOnShowMore = useCallback(() => {}, []); - - return ( - <TimelineVerticalWrapper data-testid="tree-main" role="list"> - {/* {enableOutline && ( - <TimelineOutline - theme={theme} - mode={mode} - items={outlineItems} - onSelect={onOutlineSelection} - /> - )} */} - {items.map((item, index) => { - let className = ''; - - // in tree mode alternate cards position - if (alternateCards) { - className = index % 2 === 0 ? 'left' : 'right'; - } else { - className = 'right'; - } - - const contentDetails = - (contentDetailsChildren && - (contentDetailsChildren as React.ReactNode[])[index]) || - null; - - const customIcon = Array.isArray(iconChildren) - ? iconChildren[index] - : index === 0 - ? iconChildren - : null; - - return ( - <TimelineVerticalItem - {...item} - alternateCards={alternateCards} - className={className} - contentDetailsChildren={contentDetails} - iconChild={customIcon} - hasFocus={hasFocus} - index={index} - key={item.id} - onActive={handleOnActive} - onClick={onClick} - onElapsed={onElapsed} - onShowMore={handleOnShowMore} - slideShowRunning={slideShowRunning} - cardLess={cardLess} - nestedCardHeight={nestedCardHeight} - /> - ); - })} - </TimelineVerticalWrapper> - ); -}; - -TimelineVertical.displayName = 'TimelineVertical'; - -export default TimelineVertical; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-popover-elements.tsx | -
-
- |
- 97.03% | -131/135 | -100% | -10/10 | -42.85% | -3/7 | -97.03% | -131/135 | -
timeline-toolbar.tsx | -
-
- |
- 86.84% | -99/114 | -60% | -6/10 | -25% | -1/4 | -86.84% | -99/114 | -
timeline.style.ts | -
-
- |
- 95.06% | -154/162 | -57.14% | -12/21 | -100% | -5/5 | -95.06% | -154/162 | -
timeline.tsx | -
-
- |
- 72.85% | -357/490 | -75.6% | -31/41 | -18.18% | -2/11 | -72.85% | -357/490 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -15x -15x -15x -15x -15x -15x -10x -10x -10x - - -10x -10x -10x -10x -10x -10x - - -10x -10x -10x -10x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -2x -11x -15x -15x -15x -15x -15x -15x -15x -1x -1x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -4x -4x -4x -4x -4x -4x -2x -2x -2x -2x -2x -2x -2x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import { FunctionComponent, useContext, useMemo } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import { List } from '../elements/list/list'; -import { ListItemModel } from '../elements/list/list.model'; -import { PopOver } from '../elements/popover'; - -type LayoutSwitcherProp = { - initialTimelineMode?: TimelineMode | 'HORIZONTAL_ALL'; - isDarkMode: boolean; - mode?: TimelineMode; - onUpdateTimelineMode: (s: string) => void; - position: 'top' | 'bottom'; - theme: Theme; -}; - -type QuickJumpProp = { - activeItem: number; - isDarkMode: boolean; - items: ListItemModel[]; - onActivateItem: (id: string) => void; - position: 'top' | 'bottom'; - theme: Theme; -}; - -const LayoutSwitcher: FunctionComponent<LayoutSwitcherProp> = ({ - onUpdateTimelineMode, - theme, - mode, - isDarkMode, - position, -}: LayoutSwitcherProp) => { - const { showAllCardsHorizontal, buttonTexts } = useContext(GlobalContext); - - const activeTimelineMode = useMemo( - () => mode, - [showAllCardsHorizontal, mode], - ); - - const verticalItems = useMemo( - () => [ - { - id: 'VERTICAL', - onSelect: () => onUpdateTimelineMode('VERTICAL'), - selected: activeTimelineMode === 'VERTICAL', - title: 'Default', - }, - { - id: 'VERTICAL_ALTERNATING', - onSelect: () => onUpdateTimelineMode('VERTICAL_ALTERNATING'), - selected: activeTimelineMode === 'VERTICAL_ALTERNATING', - title: 'Alternating', - }, - ], - [activeTimelineMode], - ); - - // horizontal list OF options when the mode is `HORIZONTAL` - const horizontalItems = useMemo( - () => [ - { - id: 'HORIZONTAL', - onSelect: () => { - onUpdateTimelineMode('HORIZONTAL'); - }, - selected: activeTimelineMode === 'HORIZONTAL', - title: 'Default', - }, - { - id: 'HORIZONTAL_ALL', - onSelect: () => { - onUpdateTimelineMode('HORIZONTAL_ALL'); - }, - selected: activeTimelineMode === 'HORIZONTAL_ALL', - title: 'Show all cards', - }, - ], - [activeTimelineMode], - ); - - return ( - <PopOver - placeholder={buttonTexts.changeLayout} - position={position} - theme={theme} - isDarkMode={isDarkMode} - > - <List - items={ - mode === 'HORIZONTAL' || mode === 'HORIZONTAL_ALL' - ? horizontalItems - : verticalItems - } - theme={theme} - multiSelectable - /> - </PopOver> - ); -}; - -const QuickJump: FunctionComponent<QuickJumpProp> = ({ - activeItem, - items, - theme, - onActivateItem, - isDarkMode, - position, -}: QuickJumpProp) => { - const { buttonTexts } = useContext(GlobalContext); - return ( - <PopOver - placeholder={buttonTexts.jumpTo} - position={position} - theme={theme} - width={'400px'} - isDarkMode={isDarkMode} - > - <List - items={items.map((item, index) => ({ - active: index === activeItem, - description: item.description, - id: item.id, - label: item.title, - onSelect: () => {}, - title: item.title, - }))} - theme={theme} - onClick={onActivateItem} - /> - </PopOver> - ); -}; - -export { LayoutSwitcher, QuickJump }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -11x -11x -11x -11x -7x - -7x -11x -11x -11x -11x -7x - -7x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x - - - - - - - - - - - - -10x -11x -10x -10x -10x -10x -10x -10x -10x - -11x -11x -11x -1x -1x -1x - | // Import necessary dependencies -import { FunctionComponent, useContext, useMemo } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import TimelineControl from '../timeline-elements/timeline-control/timeline-control'; -import { Toolbar } from '../toolbar'; -import { LayoutSwitcher, QuickJump } from './timeline-popover-elements'; -import { TimelineToolbarProps } from './timeline-toolbar.model'; - -// Define the TimelineToolbar component -const TimelineToolbar: FunctionComponent<TimelineToolbarProps> = ({ - activeTimelineItem, - slideShowEnabled, - slideShowRunning, - flipLayout, - toggleDarkMode, - onPaused, - onFirst, - onLast, - onNext, - onPrevious, - onRestartSlideshow, - totalItems, - items = [], - id, - onActivateTimelineItem, - onUpdateTimelineMode, - mode, -}) => { - // Access the global context - const { theme, cardLess, enableQuickJump, darkMode, toolbarPosition } = - useContext(GlobalContext); - - // Define the toolbar items - const toolbarItems = useMemo(() => { - return [ - { - label: 'Timeline Controls', - name: 'timeline_control', - onSelect: () => {}, - }, - { - label: 'timeline_popover', - name: 'popover', - onSelect: () => {}, - }, - { - label: 'layout_popover', - name: 'popover', - onSelect: () => {}, - }, - ]; - }, []); - - // Determine if the left arrow should be disabled - const disableLeft = useMemo(() => { - return flipLayout - ? activeTimelineItem === totalItems - 1 - : activeTimelineItem === 0; - }, [flipLayout, activeTimelineItem, totalItems]); - - // Determine if the right arrow should be disabled - const disableRight = useMemo(() => { - return flipLayout - ? activeTimelineItem === 0 - : activeTimelineItem === totalItems - 1; - }, [flipLayout, activeTimelineItem, totalItems]); - - // Render the TimelineToolbar component - return ( - <Toolbar items={toolbarItems} theme={theme}> - <TimelineControl - disableLeft={disableLeft} - disableRight={disableRight} - id={id} - onFirst={onFirst} - onLast={onLast} - onNext={onNext} - onPrevious={onPrevious} - onReplay={onRestartSlideshow} - slideShowEnabled={slideShowEnabled} - slideShowRunning={slideShowRunning} - isDark={darkMode} - onToggleDarkMode={toggleDarkMode} - onPaused={onPaused} - /> - {enableQuickJump ? ( - <QuickJump - activeItem={activeTimelineItem} - isDarkMode={darkMode} - items={items.map((item) => ({ - ...item, - description: item.cardSubtitle, - title: item.title, - }))} - onActivateItem={onActivateTimelineItem} - theme={theme} - position={toolbarPosition} - /> - ) : null} - {!cardLess ? ( - <LayoutSwitcher - isDarkMode={darkMode} - theme={theme} - onUpdateTimelineMode={onUpdateTimelineMode} - mode={mode} - position={toolbarPosition} - /> - ) : null} - </Toolbar> - ); -}; - -// Export the TimelineToolbar component -export { TimelineToolbar }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -10x - -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled from 'styled-components'; -import { ScrollBar } from '../common/styles'; - -export const Wrapper = styled.div<{ - $hideControls?: boolean; - cardPositionHorizontal?: 'TOP' | 'BOTTOM'; -}>` - display: flex; - flex-direction: column; - /* cannot remove this */ - height: 100%; - - &:focus { - outline: 0; - } - - overflow: hidden; - position: relative; - width: 100%; - - ${(p) => - p.cardPositionHorizontal === 'TOP' && !p.$hideControls - ? ` - & > div:nth-of-type(1) { - order: 2; - } - & > div:nth-of-type(2) { - order: 3; - } - & > div:nth-of-type(3) { - order: 1; - } - ` - : ''}; - - ${(p) => - p.cardPositionHorizontal === 'TOP' && p.$hideControls - ? ` - & > div:nth-of-type(1) { - order: 2; - } - & > div:nth-of-type(2) { - order: 1; - } - ` - : ''}; - - &.horizontal { - justify-content: flex-start; - } - - &.js-focus-visible :focus:not(.focus-visible) { - // outline: 0; - } - - &.js-focus-visible .focus-visible { - outline: 2px solid #528deb; - } -`; - -export const TimelineMainWrapper = styled.div<{ - $scrollable?: boolean | { scrollbar: boolean }; - mode?: TimelineMode; - position?: 'top' | 'bottom'; - theme?: Theme; -}>` - align-items: flex-start; - display: flex; - justify-content: center; - overflow-y: auto; - overflow-x: hidden; - overscroll-behavior: contain; - ${(p) => (p.mode === 'HORIZONTAL' ? 'position: relative' : '')}; - scroll-behavior: smooth; - width: 100%; - order: ${(p) => (p.position === 'top' ? 1 : 0)}; - - ${ScrollBar} - - &.horizontal { - min-height: 150px; - } - - padding: ${({ $scrollable }) => (!$scrollable ? '0 1rem 0' : '')}; -`; - -export const TimelineMain = styled.div` - position: absolute; - top: 50%; - left: 0; - display: flex; - align-items: center; - transition: all 0.2s ease; - transform: translate(0, -50%); - - &.vertical { - align-items: flex-start; - justify-content: flex-start; - width: 100%; - height: 100%; - } -`; - -export const Outline = styled.div<{ color?: string; height?: number }>` - position: absolute; - right: 0; - left: 0; - width: 100%; - height: ${(p) => `${p.height}px`}; - margin-right: auto; - margin-left: auto; - background: ${(p) => p.color}; -`; - -export const TimelineControlContainer = styled.div<{ - active?: boolean; - mode?: TimelineMode; -}>` - display: flex; - align-items: center; - justify-content: center; - min-height: 3rem; - - filter: ${(p) => { - if (p.active) { - return `opacity(1);`; - } else { - return `opacity(0.9);`; - } - }}; - - &.hide { - visibility: hidden; - } - - &.show { - visibility: visible; - } -`; - -export const TimelineContentRender = styled.div<{ $showAllCards?: boolean }>` - display: flex; - align-items: flex-start; - justify-content: ${(p) => (p.$showAllCards ? 'flex-start' : 'center')}; - width: 98%; - margin-right: auto; - margin-left: auto; - overflow-x: hidden; -`; - -export const ToolbarWrapper = styled.div<{ position: 'top' | 'bottom' }>` - display: flex; - font-weight: bold; - text-align: center; - text-decoration: none; - border-radius: 6px; - width: 100%; - margin: ${(p) => (p.position === 'top' ? '0 0 20px 0' : '20px 0 0 0')}; - order: ${(p) => (p.position === 'top' ? 0 : 1)}; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478 -479 -480 -481 -482 -483 -484 -485 -486 -487 -488 -489 -490 -491 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -7x -7x - - -7x -7x - - -7x -11x -11x -11x -11x -11x -11x -11x -11x -11x - - - - -11x -11x -11x -11x -11x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x - - - - -11x -11x -11x -1x -1x -1x -1x -11x -11x -11x -1x -1x -1x -1x -11x -11x -11x -11x -11x - - - - - - - - - - - - - - - - - - - - - - -11x -11x -11x -11x - - - - - - - - - - - - - - -11x -11x -7x -7x -7x - - -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - -1x -7x -11x -11x -11x - - - - - -11x -11x -7x -7x - - -7x -1x -7x -6x -6x -11x -11x -11x -7x -7x -7x -7x -7x -5x -5x -15x -15x -15x -5x -5x -7x -7x -7x - - - - - - - - -7x -7x -6x -6x - - - - - - - - - - - - - - - - - - -6x -6x -6x -6x -6x -6x -7x -7x -7x -6x -6x -7x -7x -11x -11x -11x -11x - - - - - -11x -11x -11x -11x - - - - - - - - - - - -11x -11x -11x -7x -7x -7x -7x -11x -11x -11x -11x -11x -11x -11x -11x -3x -3x -11x -11x - - - - -11x -11x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x - - - - - - - - - - - - - - - - - -11x -11x -11x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x - -7x -7x -7x -7x -7x -7x -7x -3x -11x -11x -11x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - -1x -1x -1x -1x -1x -1x -9x -11x -11x -11x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x - -2x -2x -2x -2x -2x -2x -2x -8x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -1x -1x -1x -1x - | import { Scroll } from '@models/TimelineHorizontalModel'; -import { TimelineCardModel } from '@models/TimelineItemModel'; -import { TimelineModel } from '@models/TimelineModel'; -import { getUniqueID } from '@utils/index'; -import cls from 'classnames'; -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { GlobalContext } from '../GlobalContext'; -import { useMatchMedia } from '../effects/useMatchMedia'; -import useNewScrollPosition from '../effects/useNewScrollPosition'; -import TimelineHorizontal from '../timeline-horizontal/timeline-horizontal'; -import TimelineVertical from '../timeline-vertical/timeline-vertical'; -import { TimelineToolbar } from './timeline-toolbar'; -import { - Outline, - TimelineContentRender, - TimelineMain, - TimelineMainWrapper, - ToolbarWrapper, - Wrapper, -} from './timeline.style'; - -const Timeline: React.FunctionComponent<TimelineModel> = ( - props: TimelineModel, -) => { - // de-structure the props - const { - activeTimelineItem, - contentDetailsChildren, - iconChildren, - items = [], - onFirst, - onLast, - onNext, - onPrevious, - onRestartSlideshow, - onTimelineUpdated, - onItemSelected, - onOutlineSelection, - slideShowEnabled, - slideShowRunning, - mode = 'HORIZONTAL', - enableOutline = false, - hideControls = false, - nestedCardHeight, - isChild = false, - onPaused, - uniqueId, - noUniqueId, - } = props; - - const { - cardPositionHorizontal, - disableNavOnKey, - flipLayout, - itemWidth = 200, - lineWidth, - onScrollEnd, - scrollable = true, - showAllCardsHorizontal, - theme, - darkMode, - toggleDarkMode, - verticalBreakPoint = 768, - enableBreakPoint, - updateHorizontalAllCards, - toolbarPosition, - } = useContext(GlobalContext); - - const [newOffSet, setNewOffset] = useNewScrollPosition(mode, itemWidth); - const observer = useRef<IntersectionObserver | null>(null); - const [hasFocus, setHasFocus] = useState(false); - const horizontalContentRef = useRef<HTMLDivElement | null>(null); - const [timelineMode, setTimelineMode] = useState( - mode === 'HORIZONTAL' && showAllCardsHorizontal ? 'HORIZONTAL_ALL' : mode, - ); - - const activeItemIndex = useRef<number>(activeTimelineItem); - - // reference to the timeline - const timelineMainRef = useRef<HTMLDivElement>(null); - - const canScrollTimeline = useMemo(() => { - if (!slideShowRunning) { - if (typeof scrollable === 'boolean') { - return scrollable; - } - - if (typeof scrollable === 'object' && scrollable.scrollbar) { - return scrollable.scrollbar; - } - } - }, [slideShowRunning, scrollable]); - - const id = useRef( - `react-chrono-timeline-${noUniqueId ? uniqueId : getUniqueID()}`, - ); - - useMatchMedia( - `(min-width: 100px) and (max-width: ${verticalBreakPoint}px)`, - () => { - if (mode === 'VERTICAL_ALTERNATING') { - setTimelineMode('VERTICAL'); - } - }, - enableBreakPoint, - ); - - // handlers for navigation - const handleNext = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = Math.min( - activeItemIndex.current + 1, - items.length - 1, - ); - onNext?.(); - } - }, [hasFocus, onNext]); - - const handlePrevious = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = Math.max(activeItemIndex.current - 1, 0); - onPrevious?.(); - } - }, [hasFocus, onPrevious]); - - const handleFirst = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = 0; - onFirst?.(); - } - }, [hasFocus, onFirst]); - - const handleLast = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = items.length - 1; - onLast?.(); - } - }, [hasFocus, onLast]); - - // handler for keyboard navigation - const handleKeySelection = useCallback( - (event: React.KeyboardEvent<HTMLDivElement>) => { - const { key } = event; - - if (mode === 'HORIZONTAL' && key === 'ArrowRight') { - flipLayout ? handlePrevious() : handleNext(); - } else if (mode === 'HORIZONTAL' && key === 'ArrowLeft') { - flipLayout ? handleNext() : handlePrevious(); - } else if ( - (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && - key === 'ArrowDown' - ) { - handleNext(); - } else if ( - (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && - key === 'ArrowUp' - ) { - handlePrevious(); - } else if (key === 'Home') { - handleFirst(); - } else if (key === 'End') { - handleLast(); - } - }, - [handleNext, handlePrevious, handleLast], - ); - - const handleTimelineItemClick = (itemId?: string, isSlideShow?: boolean) => { - if (itemId) { - for (let idx = 0; idx < items.length; idx++) { - if (items[idx].id === itemId) { - activeItemIndex.current = idx; - if (isSlideShow && idx < items.length - 1) { - onTimelineUpdated?.(idx + 1); - } else { - onTimelineUpdated?.(idx); - } - break; - } - } - } - }; - - useEffect(() => { - const activeItem = items[activeTimelineItem || 0]; - - if (slideShowRunning) { - activeItemIndex.current = activeTimelineItem; - } - - if (items.length && activeItem) { - // const item = items[activeItem]; - const { title, cardTitle, cardSubtitle, cardDetailedText } = activeItem; - onItemSelected?.({ - cardDetailedText, - cardSubtitle, - cardTitle, - index: activeItemIndex.current, - title, - }); - - if (mode === 'HORIZONTAL') { - const card = horizontalContentRef.current?.querySelector( - `#timeline-card-${activeItem.id}`, - ); - - const cardRect = card?.getBoundingClientRect(); - const contentRect = - horizontalContentRef.current?.getBoundingClientRect(); - - if (cardRect && contentRect) { - const { width: cardWidth, left: cardLeft } = cardRect; - const { width: contentWidth, left: contentLeft } = contentRect; - setTimeout(() => { - const ele = horizontalContentRef.current as HTMLElement; - ele.style.scrollBehavior = 'smooth'; - ele.scrollLeft += - cardLeft - contentLeft + cardWidth / 2 - contentWidth / 2; - }, 100); - } - } - } - }, [activeTimelineItem, items.length, slideShowRunning]); - - const handleScroll = (scroll: Partial<Scroll>) => { - const element = timelineMainRef.current; - if (element) { - setNewOffset(element, scroll); - } - }; - - useEffect(() => { - const ele = timelineMainRef.current; - if (!ele) { - return; - } - if (mode === 'HORIZONTAL') { - ele.scrollLeft = Math.max(newOffSet, 0); - } else { - ele.scrollTop = newOffSet; - } - }, [newOffSet]); - - useEffect(() => { - // setup observer for the timeline elements - setTimeout(() => { - const element = timelineMainRef.current; - - if (element) { - const childElements = element.querySelectorAll('.vertical-item-row'); - Array.from(childElements).forEach((elem) => { - if (observer.current) { - observer.current.observe(elem); - } - }); - } - }, 0); - - const toggleMedia = (elem: HTMLElement, state: string) => { - elem - .querySelectorAll('img,video') - .forEach( - (ele) => - ((ele as HTMLElement).style.visibility = - state === 'hide' ? 'hidden' : 'visible'), - ); - }; - - if (mode !== 'HORIZONTAL') { - observer.current = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - const element = entry.target as HTMLDivElement; - if (entry.isIntersecting) { - // show img and video when visible. - toggleMedia(element, 'show'); - } else { - // hide img and video when not visible. - toggleMedia(element, 'hide'); - // pause YouTube embeds - element.querySelectorAll('iframe').forEach((element) => { - element.contentWindow?.postMessage( - '{"event":"command","func":"stopVideo","args":""}', - '*', - ); - }); - } - }); - }, - { - root: timelineMainRef.current, - threshold: 0, - }, - ); - } - - return () => { - if (observer.current) { - observer.current.disconnect(); - } - }; - // eslint-disable-next-line - }, []); - - const handleKeyDown = useCallback( - (evt: React.KeyboardEvent<HTMLDivElement>) => { - if (!disableNavOnKey && !slideShowRunning) { - setHasFocus(true); - handleKeySelection(evt); - } - }, - [disableNavOnKey, slideShowRunning, handleKeySelection], - ); - - const handleTimelineUpdate = useCallback((mode: string) => { - if (mode === 'VERTICAL') { - setTimelineMode('VERTICAL'); - } else if (mode === 'HORIZONTAL') { - setTimelineMode('HORIZONTAL'); - updateHorizontalAllCards?.(false); - } else if (mode === 'VERTICAL_ALTERNATING') { - setTimelineMode('VERTICAL_ALTERNATING'); - } else if (mode === 'HORIZONTAL_ALL') { - setTimelineMode('HORIZONTAL_ALL'); - updateHorizontalAllCards?.(true); - } - }, []); - - const wrapperClass = useMemo(() => { - return cls(mode.toLocaleLowerCase(), { - 'focus-visible': !isChild, - 'js-focus-visible': !isChild, - }); - }, [mode, isChild]); - - return ( - <Wrapper - onKeyDown={handleKeyDown} - className={wrapperClass} - cardPositionHorizontal={cardPositionHorizontal} - onMouseDown={() => { - setHasFocus(true); - }} - $hideControls={hideControls} - onKeyUp={(evt) => { - if (evt.key === 'Escape') { - onPaused?.(); - } - }} - > - {!isChild ? ( - <ToolbarWrapper position={toolbarPosition}> - <TimelineToolbar - activeTimelineItem={activeTimelineItem} - totalItems={items.length} - slideShowEnabled={slideShowEnabled} - slideShowRunning={slideShowRunning} - onFirst={handleFirst} - onLast={handleLast} - onNext={handleNext} - onPrevious={handlePrevious} - onRestartSlideshow={onRestartSlideshow} - darkMode={darkMode} - toggleDarkMode={toggleDarkMode} - onPaused={onPaused} - id={id.current} - flipLayout={flipLayout} - items={items} - onActivateTimelineItem={handleTimelineItemClick} - onUpdateTimelineMode={handleTimelineUpdate} - mode={timelineMode} - /> - </ToolbarWrapper> - ) : null} - <TimelineMainWrapper - ref={timelineMainRef} - $scrollable={canScrollTimeline} - className={`${mode.toLowerCase()} timeline-main-wrapper`} - id="timeline-main-wrapper" - data-testid="timeline-main-wrapper" - theme={theme} - mode={mode} - position={toolbarPosition} - onScroll={(ev) => { - const target = ev.target as HTMLElement; - let scrolled = 0; - - if (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') { - scrolled = target.scrollTop + target.clientHeight; - - if (target.scrollHeight - scrolled < 1) { - onScrollEnd?.(); - } - } else { - scrolled = target.scrollLeft + target.offsetWidth; - - if (target.scrollWidth === scrolled) { - onScrollEnd?.(); - } - } - }} - > - {/* VERTICAL ALTERNATING */} - {timelineMode === 'VERTICAL_ALTERNATING' ? ( - <TimelineVertical - activeTimelineItem={activeTimelineItem} - autoScroll={handleScroll} - contentDetailsChildren={contentDetailsChildren} - hasFocus={hasFocus} - iconChildren={iconChildren} - items={items as TimelineCardModel[]} - mode={timelineMode} - onClick={handleTimelineItemClick} - onElapsed={(itemId?: string) => - handleTimelineItemClick(itemId, true) - } - onOutlineSelection={onOutlineSelection} - slideShowRunning={slideShowRunning} - theme={theme} - enableOutline={enableOutline} - nestedCardHeight={nestedCardHeight} - /> - ) : null} - - {/* HORIZONTAL */} - {timelineMode === 'HORIZONTAL' || timelineMode === 'HORIZONTAL_ALL' ? ( - <TimelineMain className={mode.toLowerCase()}> - <Outline color={theme && theme.primary} height={lineWidth} /> - <TimelineHorizontal - autoScroll={handleScroll} - contentDetailsChildren={contentDetailsChildren} - handleItemClick={handleTimelineItemClick} - hasFocus={hasFocus} - iconChildren={iconChildren} - items={items as TimelineCardModel[]} - mode={timelineMode} - onElapsed={(itemId?: string) => - handleTimelineItemClick(itemId, true) - } - slideShowRunning={slideShowRunning} - wrapperId={id.current} - nestedCardHeight={nestedCardHeight} - /> - </TimelineMain> - ) : null} - - {/* VERTICAL */} - {timelineMode === 'VERTICAL' ? ( - <TimelineVertical - activeTimelineItem={activeTimelineItem} - alternateCards={false} - autoScroll={handleScroll} - contentDetailsChildren={contentDetailsChildren} - hasFocus={hasFocus} - iconChildren={iconChildren} - items={items as TimelineCardModel[]} - mode={mode} - onClick={handleTimelineItemClick} - onElapsed={(itemId?: string) => - handleTimelineItemClick(itemId, true) - } - onOutlineSelection={onOutlineSelection} - slideShowRunning={slideShowRunning} - theme={theme} - enableOutline={enableOutline} - nestedCardHeight={nestedCardHeight} - /> - ) : null} - </TimelineMainWrapper> - - {/* placeholder to render timeline content for horizontal mode */} - <TimelineContentRender - id={id.current} - $showAllCards={showAllCardsHorizontal} - ref={horizontalContentRef} - /> - </Wrapper> - ); -}; - -Timeline.displayName = 'Timeline'; - -export default Timeline; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 100% | -43/43 | -75% | -3/4 | -100% | -1/1 | -100% | -43/43 | -
toolbar.styles.ts | -
-
- |
- 100% | -33/33 | -100% | -1/1 | -100% | -0/0 | -100% | -33/33 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x -11x -11x -11x -11x -30x -30x -30x -30x -30x -30x -11x -11x -11x -11x -1x -1x - | import { FunctionComponent, ReactNode } from 'react';
-import {
- ContentWrapper,
- IconWrapper,
- ToolbarListItem,
- ToolbarWrapper,
-} from './toolbar.styles';
-import { Theme } from '@models/Theme';
-
-export type ToolbarItem = {
- icon?: ReactNode;
- id?: string;
- label?: string;
- name: string;
- onSelect: (id: string, name: string) => void;
-};
-
-export type ToolbarProps = {
- children?: ReactNode | ReactNode[];
- items?: ToolbarItem[];
- theme: Theme;
-};
-
-const Toolbar: FunctionComponent<ToolbarProps> = ({
- items,
- children = [],
- theme,
-}) => {
- return (
- <ToolbarWrapper theme={theme}>
- {items?.map(({ label, id, icon }, index) => {
- return (
- <ToolbarListItem aria-label={label} key={id}>
- {icon ? <IconWrapper>{icon}</IconWrapper> : null}
- <ContentWrapper>{children[index]}</ContentWrapper>
- </ToolbarListItem>
- );
- })}
- </ToolbarWrapper>
- );
-};
-
-export { Toolbar };
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled from 'styled-components'; - -export const ToolbarWrapper = styled.ul<{ theme: Theme }>` - list-style: none; - margin: 0; - display: flex; - align-items: center; - background-color: ${(p) => p.theme.toolbarBgColor}; - box-shadow: 0 2px 1px rgba(0, 0, 0, 0.1); - width: 100%; - height: 100%; - padding: 10px 20px; - border-radius: 6px; - flex-wrap: wrap; -`; - -export const ToolbarListItem = styled.li` - padding: 0; - margin: 0 0.5rem; -`; - -export const IconWrapper = styled.span` - display: flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; -`; - -export const ContentWrapper = styled.span` - display: flex; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
components/common/styles | -
-
- |
- 100% | -19/19 | -100% | -3/3 | -100% | -2/2 | -100% | -19/19 | -
components/common/test | -
-
- |
- 100% | -83/83 | -100% | -1/1 | -14.28% | -1/7 | -100% | -83/83 | -
components/common/themes | -
-
- |
- 100% | -47/47 | -100% | -0/0 | -100% | -0/0 | -100% | -47/47 | -
components/effects | -
-
- |
- 62.01% | -111/179 | -75% | -12/16 | -75% | -3/4 | -62.01% | -111/179 | -
components/elements/list | -
-
- |
- 98.84% | -256/259 | -97.14% | -34/35 | -85.71% | -6/7 | -98.84% | -256/259 | -
components/elements/popover | -
-
- |
- 97.76% | -175/179 | -92.3% | -24/26 | -88.88% | -8/9 | -97.76% | -175/179 | -
components/timeline | -
-
- |
- 82.24% | -741/901 | -71.95% | -59/82 | -40.74% | -11/27 | -82.24% | -741/901 | -
components/timeline-elements/memoized | -
-
- |
- 68.39% | -119/174 | -80% | -12/15 | -100% | -1/1 | -68.39% | -119/174 | -
components/timeline-elements/timeline-card | -
-
- |
- 96.82% | -335/346 | -75% | -33/44 | -91.66% | -11/12 | -96.82% | -335/346 | -
components/timeline-elements/timeline-card-content | -
-
- |
- 81.91% | -1051/1283 | -66.25% | -106/160 | -83.33% | -30/36 | -81.91% | -1051/1283 | -
components/timeline-elements/timeline-card-media | -
-
- |
- 82.04% | -571/696 | -53.84% | -42/78 | -66.66% | -14/21 | -82.04% | -571/696 | -
components/timeline-elements/timeline-control | -
-
- |
- 98.1% | -310/316 | -53.19% | -25/47 | -100% | -3/3 | -98.1% | -310/316 | -
components/timeline-elements/timeline-item-title | -
-
- |
- 100% | -73/73 | -82.35% | -14/17 | -100% | -6/6 | -100% | -73/73 | -
components/timeline-horizontal | -
-
- |
- 100% | -138/138 | -55.55% | -5/9 | -100% | -1/1 | -100% | -138/138 | -
components/timeline-vertical | -
-
- |
- 92.97% | -635/683 | -59.09% | -39/66 | -90.9% | -10/11 | -92.97% | -635/683 | -
components/toolbar | -
-
- |
- 100% | -76/76 | -80% | -4/5 | -100% | -1/1 | -100% | -76/76 | -
utils | -
-
- |
- 81.81% | -72/88 | -63.63% | -7/11 | -75% | -6/8 | -81.81% | -72/88 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.ts | -
-
- |
- 100% | -19/19 | -100% | -3/3 | -100% | -2/2 | -100% | -19/19 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { css } from 'styled-components'; - -export const ScrollBar = css` - scrollbar-color: ${(p) => p.theme?.primary} default; - scrollbar-width: thin; - - &::-webkit-scrollbar { - width: 0.3em; - } - - &::-webkit-scrollbar-track { - box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2); - } - - &::-webkit-scrollbar-thumb { - background-color: ${(p) => p.theme?.primary}; - outline: 1px solid ${(p) => p.theme?.primary}; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 100% | -83/83 | -100% | -1/1 | -14.28% | -1/7 | -100% | -83/83 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -17x -17x -17x -17x -17x -17x -17x -17x - | import { TimelineProps } from '@models/TimelineModel'; -import { render, RenderResult } from '@testing-library/react'; -import { ReactElement } from 'react'; -import { GlobalContext } from '../../GlobalContext'; - -export const providerProps: TimelineProps = { - buttonTexts: { - dark: 'dark', - first: 'first', - last: 'last', - light: 'light', - next: 'next', - play: 'start slideshow', - previous: 'previous', - stop: 'stop slideshow', - }, - classNames: { - card: 'card', - cardMedia: 'card-media', - cardSubTitle: 'card-subtitle', - cardText: 'card-text', - cardTitle: 'card-title', - controls: 'controls', - title: 'title', - }, - darkMode: false, - enableDarkToggle: true, - fontSizes: { - cardSubtitle: '0.85rem', - cardText: '1rem', - cardTitle: '1.25rem', - title: '1.5rem', - }, - mediaHeight: 200, - mode: 'VERTICAL_ALTERNATING', - scrollable: { - scrollbar: false, - }, - showAllCardsHorizontal: false, - showProgressOnSlideshow: false, - slideItemDuration: 2000, - slideShowType: 'reveal', - textOverlay: false, - theme: { - cardBgColor: '#fff', - cardDetailsBackGround: '#ffffff', - cardDetailsColor: '#000', - cardSubtitleColor: '#000', - cardTitleColor: '#000', - detailsColor: '#000', - primary: '#0f52ba', - secondary: '#ffdf00', - titleColor: '#0f52ba', - titleColorActive: '#0f52ba', - }, - timelinePointDimension: 16, - timelinePointShape: 'circle', - titleDateFormat: 'MMM DD, YYYY', - useReadMore: true, -}; - -export const commonProps = { - disableLeft: false, - disableRight: false, - onFirst: () => {}, - onLast: () => {}, - onNext: () => {}, - onPrevious: () => {}, - onReplay: () => {}, - onToggleDarkMode: () => {}, - slideShowEnabled: false, - slideShowRunning: false, -}; - -export const customRender = ( - ui: ReactElement, - { providerProps, ...renderOptions }: any, -): RenderResult => { - return render( - <GlobalContext.Provider value={providerProps}>{ui}</GlobalContext.Provider>, - renderOptions, - ); -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.ts | -
-
- |
- 100% | -47/47 | -100% | -0/0 | -100% | -0/0 | -100% | -47/47 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; - -export const defaultTheme: Theme = { - cardBgColor: '#ffffff', - cardDetailsBackGround: '#ffffff', - cardDetailsColor: '#000', - cardMediaBgColor: '#f5f5f5', - cardSubtitleColor: '#000', - cardTitleColor: '#007FFF', - detailsColor: '#000', - iconBackgroundColor: '#007FFF', - nestedCardBgColor: '#f5f5f5', - nestedCardDetailsBackGround: '#f5f5f5', - nestedCardDetailsColor: '#000', - nestedCardSubtitleColor: '#000', - nestedCardTitleColor: '#000', - primary: '#007FFF', - secondary: '#ffdf00', - titleColor: '#007FFF', - titleColorActive: '#007FFF', - toolbarBgColor: '#f5f5f5', - toolbarBtnBgColor: '#fff', - toolbarTextColor: '#000', -}; - -export const darkTheme: Theme = { - cardBgColor: '#191919', - cardDetailsBackGround: '#191919', - cardDetailsColor: '#ffff0f', - cardMediaBgColor: '#2f2f2f', - cardSubtitleColor: '#ffffff', - cardTitleColor: '#007FFF', - detailsColor: '#ffffff', - iconBackgroundColor: '#007FFF', - nestedCardBgColor: '#333333', - nestedCardDetailsBackGround: '#333333', - nestedCardDetailsColor: '#ffffff', - nestedCardSubtitleColor: '#ffffff', - nestedCardTitleColor: '#ffffff', - primary: '#007FFF', - secondary: '#ffdf00', - titleColor: '#007FFF', - titleColorActive: '#007FFF', - toolbarBgColor: '#555', - toolbarBtnBgColor: '#222', - toolbarTextColor: '#ffffff', -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
useCloseClickOutside.ts | -
-
- |
- 93.33% | -42/45 | -100% | -6/6 | -100% | -1/1 | -93.33% | -42/45 | -
useMatchMedia.ts | -
-
- |
- 86.95% | -40/46 | -50% | -4/8 | -50% | -1/2 | -86.95% | -40/46 | -
useNewScrollPosition.ts | -
-
- |
- 32.95% | -29/88 | -100% | -2/2 | -100% | -1/1 | -32.95% | -29/88 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 | 1x -1x -29x -29x -29x -29x -29x -29x -29x -10x -10x -10x -10x -3x -3x -10x -29x -29x -29x - - - -29x -29x -29x -21x -21x -21x -21x -21x -21x -21x -29x -29x -29x -12x -12x -12x -12x -12x -12x -12x -12x -29x -29x - | import { RefObject, useCallback, useEffect, useRef } from 'react'; - -export default function useCloseClickOutside( - el: RefObject<HTMLDivElement>, - callback: () => void, -) { - const htmlElement = useRef<HTMLElement>(); - - const handleClick = useCallback((e: MouseEvent) => { - const element = htmlElement.current; - - if (element) { - if (!element.contains(e.target as Node)) { - callback(); - } - } - }, []); - - const handleEscape = useCallback((e: KeyboardEvent) => { - if (e.key === 'Escape') { - callback(); - } - }, []); - - useEffect(() => { - const element = el.current; - - if (element) { - htmlElement.current = element; - - element.addEventListener('keyup', handleEscape); - } - }, [el, callback]); - - useEffect(() => { - document.addEventListener('click', handleClick); - return () => { - const element = htmlElement.current; - if (element) { - element.removeEventListener('keyup', handleEscape); - } - document.removeEventListener('click', handleClick); - }; - }, []); -} - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -7x - - -7x -7x -7x -7x -7x -7x - - -7x -7x -7x -7x -7x -7x -10x -10x -10x -10x - - -10x -10x -10x -10x - | /** - * The useMatchMedia hook takes a media query string, a callback function, and an enabled boolean. - * It returns a boolean indicating if the media query matches the current viewport and executes the callback if it does. - * - * @param {string} query - The media query string to match against. - * @param {() => void} [cb] - Optional callback function to be executed if the media query matches. - * @param {boolean} [enabled=true] - Whether the hook is enabled or not. - * @returns {boolean} - Whether the media query matches the current viewport. - */ -import { useEffect, useState } from 'react'; - -export const useMatchMedia = ( - query: string, - cb?: () => void, - enabled = true, -) => { - const [matches, setMatches] = useState<boolean>(false); - - useEffect(() => { - if (!enabled) { - return; - } - - const media = window.matchMedia(query); - const listener = () => setMatches(media.matches); - - // Check initial match and update state if necessary - if (media.matches !== matches) { - setMatches(media.matches); - } - - media.addEventListener('change', listener); - - return () => { - media.removeEventListener('change', listener); - }; - }, [query, enabled]); - - useEffect(() => { - if (matches && cb) { - cb(); - } - }, [matches, cb]); - - return matches; -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -10x -10x -10x -10x -10x -10x -1x -1x - | import { Scroll } from '@models/TimelineHorizontalModel'; -import { TimelineMode } from '@models/TimelineModel'; -import { useMemo, useState } from 'react'; - -/** - * Hook to calculate the new scroll position based on the given mode and item width. - * - * @param {TimelineMode} mode - The mode of the timeline (HORIZONTAL, VERTICAL, or VERTICAL_ALTERNATING). - * @param {number} [itemWidth] - Optional item width for horizontal mode. - * @returns {[number, (e: HTMLElement, s: Partial<Scroll>) => void]} - The new offset and a function to compute the new offset. - */ -const useNewScrollPosition = ( - mode: TimelineMode, - itemWidth?: number, -): [number, (e: HTMLElement, s: Partial<Scroll>) => void] => { - // State to hold the new offset value - const [newOffset, setNewOffset] = useState(0); - - // Memoized function to compute the new offset value - const computeNewOffset = useMemo( - () => (parent: HTMLElement, scroll: Partial<Scroll>) => { - // Destructuring relevant properties from parent and scroll - const { clientWidth, scrollLeft, scrollTop, clientHeight } = parent; - const { pointOffset, pointWidth, contentHeight, contentOffset } = scroll; - - // Handling horizontal mode - if (mode === 'HORIZONTAL' && itemWidth && pointWidth && pointOffset) { - // Calculating right boundaries for container and circular element - const contrRight = scrollLeft + clientWidth; - const circRight = pointOffset + pointWidth; - - // Checking if the element is fully visible - const isVisible = pointOffset >= scrollLeft && circRight <= contrRight; - - // Checking if the element is partially visible - const isPartiallyVisible = - (pointOffset < scrollLeft && circRight > scrollLeft) || - (circRight > contrRight && pointOffset < contrRight); - - // Calculating gaps from left and right - const leftGap = pointOffset - scrollLeft; - const rightGap = contrRight - pointOffset; - - // Setting offset based on visibility and gap conditions - if ( - !(isVisible || isPartiallyVisible) || - (leftGap <= itemWidth && leftGap >= 0) || - (rightGap <= itemWidth && rightGap >= 0) - ) { - setNewOffset(pointOffset - itemWidth); - } - } else if (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') { - // Handling vertical modes - if (contentOffset && contentHeight) { - // Calculating bottom boundaries for container and circular element - const contrBottom = scrollTop + clientHeight; - const circBottom = contentOffset + contentHeight; - - // Checking if the element is fully visible - const isVisible = - contentOffset >= scrollTop && circBottom <= contrBottom; - - // Checking if the element is partially visible - const isPartiallyVisible = - (contentOffset < scrollTop && circBottom > scrollTop) || - (circBottom > contrBottom && contentOffset < contrBottom); - - // Calculating new offset - const nOffset = contentOffset - contentHeight; - const notVisible = !isVisible || isPartiallyVisible; - - // Setting offset based on visibility conditions - if (notVisible && nOffset + contentHeight < contrBottom) { - setNewOffset(nOffset + Math.round(contentHeight / 2)); - } else if (notVisible) { - setNewOffset(nOffset); - } - } - } - }, - [mode, itemWidth], // Dependencies for useMemo - ); - - // Returning the new offset and the function to compute it - return [newOffset, computeNewOffset]; -}; - -export default useNewScrollPosition; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
list-item.tsx | -
-
- |
- 95.16% | -59/62 | -100% | -6/6 | -50% | -1/2 | -95.16% | -59/62 | -
list.styles.ts | -
-
- |
- 100% | -109/109 | -94.11% | -16/17 | -100% | -4/4 | -100% | -109/109 | -
list.tsx | -
-
- |
- 100% | -88/88 | -100% | -12/12 | -100% | -1/1 | -100% | -88/88 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x - - - -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -6x -6x -6x -6x -6x -6x -13x -13x -13x -13x -13x -13x -13x -13x -13x -1x -1x -1x -1x -1x - | import { FunctionComponent, KeyboardEvent, memo, useCallback } from 'react'; -import { ListItemModel } from './list.model'; -import { - CheckboxStyle, - CheckboxWrapper, - ListItemStyle, - StyleAndDescription, - TitleDescriptionStyle, - TitleStyle, -} from './list.styles'; -import { CheckIcon } from 'src/components/icons'; - -const ListItem: FunctionComponent<ListItemModel> = memo( - ({ - title, - id, - description, - theme, - onClick, - active, - selected = false, - selectable = false, - }: ListItemModel) => { - const handleOnClick = useCallback((id: string) => onClick?.(id), []); - - const handleKeyPress = useCallback((ev: KeyboardEvent, id: string) => { - if (ev.key === 'Enter') { - handleOnClick(id); - } - }, []); - - return ( - <ListItemStyle - key={id} - theme={theme} - onClick={() => handleOnClick(id)} - active={active} - tabIndex={0} - selectable={selectable} - onKeyUp={(ev) => handleKeyPress(ev, id)} - > - {selectable ? ( - <CheckboxWrapper> - <CheckboxStyle role="checkbox" selected={selected} theme={theme}> - {selected && <CheckIcon />} - </CheckboxStyle> - </CheckboxWrapper> - ) : null} - <StyleAndDescription selectable={selectable}> - <TitleStyle theme={theme}>{title}</TitleStyle> - <TitleDescriptionStyle theme={theme}> - {description}{' '} - </TitleDescriptionStyle> - </StyleAndDescription> - </ListItemStyle> - ); - }, -); - -ListItem.displayName = 'ListItem'; - -export { ListItem }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme';
-import styled from 'styled-components';
-
-// Style constants
-const BACKGROUND_COLOR = '#f5f5f5';
-const BORDER_COLOR = 'rgba(0, 0, 0, 0.1)';
-
-// Common styles
-const commonStyles = `
- align-items: center;
- background: ${BACKGROUND_COLOR};
- border-radius: 4px;
- box-shadow: 0px 1px 1px ${BORDER_COLOR};
- display: flex;
- margin: 0;
- margin-bottom: 0.5rem;
- padding: 0.25rem 0.5rem;
- width: 100%;
-
- &:last-child {
- margin-bottom: 0;
- }
-`;
-
-// List styles
-export const ListStyle = styled.ul`
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- list-style: none;
- margin: 0;
- padding: 0;popo
- width: 100%;
-`;
-
-// List item styles
-export const ListItemStyle = styled.li<{
- active?: boolean;
- selectable?: boolean;
- theme: Theme;
-}>`
- ${commonStyles}
- border: ${(p) =>
- p.active ? `1px solid ${p.theme.primary}` : '1px solid transparent'};
- flex-direction: ${(p) => (p.selectable ? 'row' : 'column')};
- background: ${(p) => p.theme.toolbarBtnBgColor};
- &:hover {
- border: 1px solid ${(p) => p.theme.primary};
- cursor: pointer;
- }
- user-select: none;
-`;
-
-// Title styles
-export const TitleStyle = styled.h1<{ theme: Theme }>`
- color: ${(p) => p.theme.primary};
- font-size: 1rem;
- font-weight: normal;
- margin: 0.2rem 0;
- text-align: left;
- white-space: nowrap;
-`;
-
-// Title description styles
-export const TitleDescriptionStyle = styled.p<{ theme: Theme }>`
- font-size: 0.8rem;
- font-weight: normal;
- margin: 0;
- padding: 0.1rem;
- text-align: left;
- width: 100%;
- color: ${(p) => p.theme.cardSubtitleColor};
-`;
-
-// Checkbox wrapper styles
-export const CheckboxWrapper = styled.span`
- width: 2rem;
- display: flex;
- align-items: center;
- justify-content: center;
-`;
-
-// Checkbox styles
-export const CheckboxStyle = styled.span<{ selected?: boolean; theme: Theme }>`
- align-items: center;
- background-color: white;
- ${(p) => !p.selected && `box-shadow: inset 0 0 0 1px ${BORDER_COLOR};`}
- background: ${(p) => (p.selected ? p.theme.primary : p.theme.toolbarBgColor)};
- color: #fff;
- border-radius: 50%;
- display: flex;
- height: 1.25rem;
- justify-content: center;
- margin-right: 0.25rem;
- margin-left: 0.1rem;
- width: 1.25rem;
-
- svg {
- width: 80%;
- height: 80%;
- }
-`;
-
-// Style and description wrapper styles
-export const StyleAndDescription = styled.div<{ selectable?: boolean }>`
- flex-direction: column;
- display: flex;
- width: ${(p) => (p.selectable ? 'calc(100% - 2rem)' : '100%')};
-`;
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -8x -8x -8x -8x -8x -8x -5x -10x -10x -5x -8x -8x -8x -8x -2x -4x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -8x -8x -8x -8x -2x -2x -2x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -8x -8x -8x -8x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -14x -8x -8x -8x -8x -1x -1x -1x - | // Import necessary dependencies and utilities -import { getUniqueID } from '@utils/index'; -import { - FunctionComponent, - startTransition, - useCallback, - useState, -} from 'react'; -import { ListItem } from './list-item'; -import { ListModel } from './list.model'; -import { ListStyle } from './list.styles'; - -// Define the List component -const List: FunctionComponent<ListModel> = ({ - items, - theme, - onClick, - activeItemIndex, - multiSelectable = false, -}) => { - // Initialize state for list items - const [listItems, setListItems] = useState(() => - items.map((item) => ({ - id: getUniqueID(), - ...item, - })), - ); - - // Callback function for handling checkbox selection - const onChecked = useCallback((id: string) => { - const updatedItems = listItems.map((item) => { - if (item.id === id) { - return { - ...item, - selected: true, - }; - } else { - return { - ...item, - selected: false, - }; - } - }); - - setListItems(updatedItems); - }, []); - - // Callback function for handling item click - const handleClick = useCallback((id: string) => { - onChecked(id); - - if (multiSelectable) { - const item = listItems.find((item) => item.id === id); - - if (item.onSelect) { - startTransition(() => { - item.onSelect(); - }); - } - } else { - onClick?.(id); - } - }, []); - - // Render the List component - return ( - <ListStyle> - {listItems?.map(({ title, id, description, selected }, index) => { - return ( - <ListItem - title={title} - id={id} - key={id} - description={description} - theme={theme} - onClick={handleClick} - selectable={multiSelectable} - selected={selected} - active={activeItemIndex === index} - /> - ); - })} - </ListStyle> - ); -}; - -// Export the List component -export { List }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 95.29% | -81/85 | -100% | -9/9 | -75% | -3/4 | -95.29% | -81/85 | -
popover.styles.ts | -
-
- |
- 100% | -94/94 | -88.23% | -15/17 | -100% | -5/5 | -100% | -94/94 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x - - - - -31x -31x -31x -31x -17x -5x -5x -5x -12x -12x -12x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -31x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -18x -31x -31x -31x -1x -1x - | import React, { FunctionComponent, useEffect, useRef, useState } from 'react'; -import useCloseClickOutside from 'src/components/effects/useCloseClickOutside'; -import { ChevronDown, CloseIcon } from 'src/components/icons'; -import { PopOverModel } from './popover.model'; -import { - CloseButton, - Content, - Header, - PopoverHolder, - PopoverWrapper, - Selecter, - SelecterIcon, - SelecterLabel, -} from './popover.styles'; - -const PopOver: FunctionComponent<PopOverModel> = ({ - children, - position, - placeholder, - theme, - width = '300px', - isDarkMode = false, -}) => { - const [open, setOpen] = useState(false); - const [isVisible, setIsVisible] = useState(false); - const ref = useRef<HTMLDivElement>(null); - - const toggleOpen = () => setOpen(!open); - - const closePopover = () => setOpen(false); - - const handleKeyPress = (ev: React.KeyboardEvent) => { - if (ev.key === 'Enter') { - toggleOpen(); - } - }; - - useCloseClickOutside(ref, closePopover); - - useEffect(() => { - if (open) { - setTimeout(() => { - setIsVisible(true); - }, 10); - } else { - setIsVisible(false); - } - }, [open]); - - return ( - <PopoverWrapper ref={ref}> - <Selecter - role="button" - onClick={toggleOpen} - theme={theme} - open={open} - isDarkMode={isDarkMode} - tabIndex={0} - onKeyUp={handleKeyPress} - > - <SelecterIcon theme={theme} open={open}> - <ChevronDown /> - </SelecterIcon> - <SelecterLabel>{placeholder}</SelecterLabel> - </Selecter> - {open ? ( - <PopoverHolder - position={position} - style={{ width: `${width}` }} - theme={theme} - visible={isVisible} - > - <Header> - <CloseButton theme={theme} onClick={closePopover}> - <CloseIcon /> - </CloseButton> - </Header> - <Content>{children}</Content> - </PopoverHolder> - ) : null} - </PopoverWrapper> - ); -}; - -export { PopOver }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -21x -15x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled from 'styled-components'; - -export const PopoverWrapper = styled.div``; - -export const PopoverHolder = styled.div<{ - position?: 'top' | 'bottom'; - theme?: Theme; - visible?: boolean; -}>` - align-items: flex-start; - background: ${({ theme }) => theme.toolbarBgColor}; - background:; - border-radius: 6px; - box-shadow: 0px 5px 16px rgba(0, 0, 0, 0.5); - display: flex; - flex-direction: column; - justify-content: space-between; - max-height: 500px; - overflow-y: auto; - padding: 0.5rem; - position: absolute; - ${(p) => (p.position === 'bottom' ? `bottom: 3.5rem` : `top: 3.5rem`)}; - width: 100%; - z-index: 100; - opacity: ${({ visible }) => (visible ? 1 : 0)}; - transition: opacity 0.1s ease-in-out; -`; - -export const Selecter = styled.div<{ - isDarkMode: boolean; - open?: boolean; - theme: Theme; -}>` - align-items: center; - background: ${({ theme }) => theme.toolbarBtnBgColor}; - color: ${({ theme }) => theme.toolbarTextColor}; - border-radius: 25px; - box-shadow: ${({ open, isDarkMode }) => - !open - ? `0px 1px 1px rgba(0, 0, 0, ${isDarkMode ? '0.85' : '0.2'})` - : 'inset 0 0 1px 1px rgba(0, 0, 0, 0.2)'}; - cursor: pointer; - display: flex; - font-weight: normal; - justify-content: space-between; - padding: 0.4rem 0.75rem 0.4rem 0.5rem; - user-select: none; -`; - -export const SelecterIcon = styled.span<{ open: boolean; theme: Theme }>` - align-items: center; - color: ${({ theme }) => theme.primary}; - display: flex; - height: 1.25rem; - justify-content: center; - width: 1.25rem; - transform: ${({ open }) => (open ? 'rotate(180deg)' : 'rotate(0deg)')}; - transition: transform 0.2s ease-in-out; - - & svg { - height: 100%; - width: 100%; - } -`; - -export const SelecterLabel = styled.span` - font-size: 0.9rem; - text-align: left; -`; - -export const Header = styled.div` - height: 30px; - width: 100%; -`; - -export const Content = styled.div` - height: calc(100% - 30px); - overflow-y: auto; - padding: 0.25rem; - width: calc(100% - 0rem); -`; - -export const CloseButton = styled.button<{ theme: Theme }>` - align-items: center; - background: transparent; - border: none; - color: ${({ theme }) => theme.primary}; - cursor: pointer; - display: flex; - justify-content: center; - margin-bottom: 0.5rem; - margin-left: auto; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 68.39% | -119/174 | -80% | -12/15 | -100% | -1/1 | -68.39% | -119/174 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -25x -25x -25x -25x -25x -25x -25x -25x -25x -25x -1x -1x -1x -24x -24x -25x -25x -4x -29x -1x -1x -1x -1x -1x -29x -24x -24x -24x -24x -24x -24x -24x -24x -24x -24x -5x -1x - -1x -1x -1x -1x -1x -1x - - - - - - - - - - - - - - - - - - -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - - - - - - - - - -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x - - -10x -10x -10x - - - - - - - - - - -10x -10x -1x - - - - -1x -1x -1x -1x -1x - | import cls from 'classnames'; -import React, { memo, useCallback, useMemo } from 'react'; -import { hexToRGBA } from '../../../utils'; -import { MaximizeIcon, MinimizeIcon, MinusIcon, PlusIcon } from '../../icons'; -import { - CardSubTitle, - CardTitle, - CardTitleAnchor, -} from '../timeline-card-content/timeline-card-content.styles'; -import { - ExpandButton, - ShowHideTextButton, -} from '../timeline-card-media/timeline-card-media-buttons'; -import { DetailsTextWrapper } from './../timeline-card-media/timeline-card-media.styles'; -import { - Content, - DetailsTextMemoModel, - ExpandButtonModel, - ShowHideTextButtonModel, - Title, -} from './memoized-model'; - -const TitleMemo = ({ - title, - url, - theme, - color, - dir, - active, - fontSize = '1rem', - classString = '', - padding = false, -}: Title) => { - return title ? ( - <CardTitle - className={cls(active ? 'active' : '', { [classString]: true })} - theme={theme} - style={{ color }} - dir={dir} - $fontSize={fontSize} - data-class={classString} - $padding={padding} - > - {url ? ( - <CardTitleAnchor href={url} target="_blank" rel="noreferrer"> - {title} - </CardTitleAnchor> - ) : ( - title - )} - </CardTitle> - ) : null; -}; - -TitleMemo.displayName = 'Timeline Title'; - -const SubTitleMemo = React.memo<Content>( - ({ content, color, dir, theme, fontSize, classString, padding }: Content) => - content ? ( - <CardSubTitle - style={{ color }} - dir={dir} - theme={theme} - $fontSize={fontSize} - className={cls('card-sub-title', classString)} - $padding={padding} - > - {content} - </CardSubTitle> - ) : null, - (prev, next) => - prev.theme?.cardSubtitleColor === next.theme?.cardSubtitleColor, -); - -SubTitleMemo.displayName = 'Timeline Content'; - -export const ExpandButtonMemo = memo<ExpandButtonModel>( - ({ theme, expanded, onExpand, textOverlay }: ExpandButtonModel) => { - const label = useMemo(() => { - return expanded ? 'Minimize' : 'Maximize'; - }, [expanded]); - - return textOverlay ? ( - <ExpandButton - onPointerDown={onExpand} - onKeyDown={(ev) => ev.key === 'Enter' && onExpand?.(ev)} - theme={theme} - aria-expanded={expanded} - tabIndex={0} - aria-label={label} - title={label} - > - {expanded ? <MinimizeIcon /> : <MaximizeIcon />} - </ExpandButton> - ) : null; - }, - (prev, next) => prev.expanded === next.expanded, -); - -ExpandButtonMemo.displayName = 'Expand Button'; - -export const ShowOrHideTextButtonMemo = memo<ShowHideTextButtonModel>( - ({ textOverlay, onToggle, theme, show }: ShowHideTextButtonModel) => { - const label = useMemo(() => { - return show ? 'Hide Text' : 'Show Text'; - }, [show]); - - return textOverlay ? ( - <ShowHideTextButton - onPointerDown={onToggle} - theme={theme} - tabIndex={0} - onKeyDown={(ev) => ev.key === 'Enter' && onToggle?.(ev)} - aria-label={label} - title={label} - > - {show ? <MinusIcon /> : <PlusIcon />} - </ShowHideTextButton> - ) : null; - }, -); - -ShowOrHideTextButtonMemo.displayName = 'Show Hide Text Button'; - -const DetailsTextMemo = memo<DetailsTextMemoModel>( - ({ - theme, - show, - expand, - textOverlay, - text, - height, - onRender, - }: DetailsTextMemoModel) => { - const onTextRef = useCallback((node: HTMLDivElement) => { - if (node) { - onRender?.(node.clientHeight); - } - }, []); - - const Text = text; - - const background = useMemo(() => { - const bg = theme?.cardDetailsBackGround || ''; - if (bg) { - return hexToRGBA(bg, 0.8); - } else { - return bg; - } - }, [theme?.cardDetailsBackGround]); - - return textOverlay ? ( - <DetailsTextWrapper - ref={onTextRef} - // height={expand ? height : 0} - $expandFull={expand} - theme={theme} - $show={show} - background={background} - > - <Text /> - </DetailsTextWrapper> - ) : null; - }, - (prev, next) => - prev.height === next.height && - prev.show === next.show && - prev.expand === next.expand && - JSON.stringify(prev.theme) === JSON.stringify(next.theme), -); - -DetailsTextMemo.displayName = 'Details Text'; - -export { TitleMemo, SubTitleMemo, DetailsTextMemo }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { keyframes } from 'styled-components'; - -export const reveal = keyframes` - 0% { - opacity: 0; - transform: scale(0.95); - } - 100% { - opacity: 1; - transform: scale(1); - } -`; - -export const slideInFromTop = keyframes` - 0% { - opacity: 0; - transform: translateY(-50%); - } - 100% { - opacity: 1; - transform: translateY(0); - } -`; - -export const slideInFromLeft = keyframes` - 0% { - opacity: 0; - transform: translateX(-50%); - } - 100% { - opacity: 1; - transform: translateX(0); - } -`; - -export const slideFromRight = keyframes` - 0% { - opacity: 0; - transform: translateX(50%); - } - 100% { - opacity: 1; - transform: translateX(0); - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -32x -32x -32x -32x -32x -32x -41x -41x -41x - - - - -41x -41x -32x -41x -41x -41x -41x -41x - - - - - - - - - - - - - - - - - -41x -41x -41x - - - - - - - - -41x -41x -41x -37x -37x -37x -37x -37x -41x -41x -41x -41x -1x -1x - | import { TimelineMode } from '@models/TimelineModel'; -import { FunctionComponent, PointerEvent, useContext, useMemo } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import ChevronIcon from '../../icons/chev-right'; -import { ContentFooterProps } from './header-footer.model'; -import { - ChevronIconWrapper, - ShowMore, - SlideShowProgressBar, - TriangleIconWrapper, -} from './timeline-card-content.styles'; - -/** - * ContentFooter - * - * A functional component that renders the footer of the timeline card. - * It displays the read more/less button, progress bar, and triangle icon. - * The read more/less button appears only if the content is large. - * The progress bar and triangle icon are displayed only if the card is in slideshow mode. - * - * @property {boolean} showProgressBar - Determines if progress bar should be displayed. - * @property {Function} onExpand - Function called when expanding content. - * @property {string} triangleDir - Direction of the triangle icon. - * @property {boolean} showMore - Determines if 'read more' should be displayed. - * @property {boolean} textContentIsLarge - Determines if text content is large. - * @property {boolean} showReadMore - Determines if 'read more' button should be displayed. - * @property {number} remainInterval - Remaining interval for progress bar. - * @property {boolean} paused - Determines if progress is paused. - * @property {number} startWidth - Starting width of progress bar. - * @property {boolean} canShow - Determines if the element can be shown. - * @property {React.RefObject} progressRef - Ref to the progress bar. - * @property {boolean} isNested - Determines if component is nested. - * @property {boolean} isResuming - Determines if slideshow is resuming. - * - * @returns {JSX.Element} ContentFooter component. - */ -const ContentFooter: FunctionComponent<ContentFooterProps> = ({ - showProgressBar, - onExpand, - triangleDir, - showMore, - textContentIsLarge, - showReadMore, - remainInterval, - paused, - startWidth, - canShow, - progressRef, - isNested, - isResuming, -}) => { - const { mode, theme } = useContext(GlobalContext); - - const canShowTriangleIcon = useMemo(() => { - return ( - !isNested && - (['VERTICAL', 'VERTICAL_ALTERNATING'] as TimelineMode[]).some( - (m) => m === mode, - ) - ); - }, [mode, isNested]); - - const handleClick = (ev: PointerEvent) => { - ev.stopPropagation(); - ev.preventDefault(); - onExpand(); - }; - - const canShowMore = useMemo(() => { - return showReadMore && textContentIsLarge; - }, [showReadMore, textContentIsLarge]); - - return ( - <> - {canShowMore ? ( - <ShowMore - className="show-more" - onPointerDown={handleClick} - onKeyUp={(event) => { - if (event.key === 'Enter') { - onExpand(); - } - }} - show={canShow ? 'true' : 'false'} - theme={theme} - tabIndex={0} - > - {<span>{showMore ? 'read less' : 'read more'}</span>} - <ChevronIconWrapper collapsed={showMore ? 'true' : 'false'}> - <ChevronIcon /> - </ChevronIconWrapper> - </ShowMore> - ) : null} - - {showProgressBar && ( - <SlideShowProgressBar - color={theme?.primary} - $duration={remainInterval} - $paused={paused} - ref={progressRef} - $startWidth={startWidth} - $resuming={isResuming} - ></SlideShowProgressBar> - )} - - {canShowTriangleIcon && ( - <TriangleIconWrapper - dir={triangleDir} - theme={theme} - offset={-8} - ></TriangleIconWrapper> - )} - </> - ); -}; - -export { ContentFooter }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -25x -25x -25x -25x -25x -25x -25x -19x -19x -19x -19x -19x -19x -19x -25x -25x -25x -19x -19x -19x -19x -19x -19x -25x -25x -25x -25x -1x -1x -1x -1x -1x -1x - | import { FunctionComponent, memo, useContext } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { SubTitleMemo, TitleMemo } from '../memoized'; -import { ContentHeaderProps } from './header-footer.model'; -import { TimelineCardHeader } from './timeline-card-content.styles'; - -/** - * ContentHeader component - * This component renders the header of the timeline card including the title and subtitle. - * It doesn't render the title and subtitle if the card has media. - * The title and subtitle are memoized to prevent unnecessary re-renders. - * - * @property {string} title - The title of the card. - * @property {string} url - The URL of the card. - * @property {boolean} media - Indicates whether the card has media or not. - * @property {string} content - The main content of the card. - * @returns {JSX.Element} The ContentHeader component. - */ -const ContentHeader: FunctionComponent<ContentHeaderProps> = memo( - ({ title, url, media, content }: ContentHeaderProps) => { - // Using context to get global values - const { fontSizes, classNames, theme } = useContext(GlobalContext); - - return ( - <TimelineCardHeader> - {/* Render title if there is no media */} - {!media && ( - <TitleMemo - title={title} - theme={theme} - url={url} - fontSize={fontSizes?.cardTitle} - classString={classNames?.cardTitle} - /> - )} - {/* Render subtitle if there is no media */} - {!media && ( - <SubTitleMemo - content={content} - theme={theme} - fontSize={fontSizes?.cardSubtitle} - classString={classNames?.cardSubTitle} - /> - )} - </TimelineCardHeader> - ); - }, -); - -// Setting display name for easier debugging -ContentHeader.displayName = 'ContentHeader'; - -export { ContentHeader }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 | 1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -41x -1x -1x -1x -1x -1x - | import { forwardRef, useContext } from 'react';
-import { GlobalContext } from '../../GlobalContext';
-import { DetailsTextProps } from './details-text.model';
-import { getTextOrContent } from './text-or-content';
-import { TimelineContentDetailsWrapper } from './timeline-card-content.styles';
-
-const DetailsText = forwardRef<HTMLDivElement, DetailsTextProps>(
- (prop, ref) => {
- const {
- showMore,
- cardActualHeight,
- detailsHeight,
- gradientColor,
- customContent,
- timelineContent,
- detailedText,
- contentDetailsClass,
- } = prop;
-
- const {
- useReadMore,
- borderLessCards,
- contentDetailsHeight,
- textOverlay,
- theme,
- } = useContext(GlobalContext);
-
- const TextContent = getTextOrContent({
- detailedText,
- showMore,
- theme,
- timelineContent,
- });
-
- return (
- <>
- {/* detailed text */}
- <TimelineContentDetailsWrapper
- aria-expanded={showMore}
- className={contentDetailsClass}
- $customContent={!!customContent}
- ref={ref}
- theme={theme}
- $useReadMore={useReadMore}
- $borderLess={borderLessCards}
- $showMore={showMore}
- $cardHeight={!textOverlay ? cardActualHeight : null}
- $contentHeight={detailsHeight}
- height={contentDetailsHeight}
- $textOverlay={textOverlay}
- $gradientColor={gradientColor}
- >
- {customContent || (
- <TextContent
- {...{ detailedText, showMore, theme, timelineContent }}
- />
- )}
- </TimelineContentDetailsWrapper>
- </>
- );
- },
-);
-
-DetailsText.displayName = 'Details Text';
-
-export { DetailsText };
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
card-animations.styles.ts | -
-
- |
- 100% | -45/45 | -100% | -0/0 | -100% | -0/0 | -100% | -45/45 | -
content-footer.tsx | -
-
- |
- 75.21% | -88/117 | -75% | -6/8 | -33.33% | -1/3 | -75.21% | -88/117 | -
content-header.tsx | -
-
- |
- 100% | -53/53 | -100% | -7/7 | -100% | -0/0 | -100% | -53/53 | -
details-text.tsx | -
-
- |
- 100% | -66/66 | -50% | -1/2 | -100% | -0/0 | -100% | -66/66 | -
text-or-content.tsx | -
-
- |
- 90.98% | -111/122 | -45% | -9/20 | -100% | -3/3 | -90.98% | -111/122 | -
timeline-card-content.styles.ts | -
-
- |
- 78.94% | -330/418 | -67.74% | -42/62 | -89.65% | -26/29 | -78.94% | -330/418 | -
timeline-card-content.tsx | -
-
- |
- 77.48% | -358/462 | -67.21% | -41/61 | -0% | -0/1 | -77.48% | -358/462 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -2x - - - - - -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -1x -1x -1x -1x -1x -1x -1x -73x -73x -73x -73x -73x -73x -73x -41x -41x -41x -41x -41x -41x - -41x -41x -41x -1x -1x -1x -1x -1x -1x -1x -41x -40x -40x -41x -41x -41x - - - - - -41x -41x -41x -35x -35x -35x -35x -35x -35x -35x -35x -6x -41x -41x -41x -41x -41x -73x -73x -73x -73x -73x -73x -1x -1x - | import { TimelineContentModel } from '@models/TimelineContentModel'; -import { TimelineProps } from '@models/TimelineModel'; -import { - ForwardRefExoticComponent, - ReactNode, - forwardRef, - useContext, -} from 'react'; -import xss from 'xss'; -import { GlobalContext } from '../../GlobalContext'; -import { - TimelineContentDetails, - TimelineSubContent, -} from './timeline-card-content.styles'; - -// Define the type for the TextOrContentModel -export type TextOrContentModel = Pick< - TimelineContentModel, - 'timelineContent' | 'theme' | 'detailedText' -> & { - showMore?: boolean; -}; - -// Function to render an array of text -const renderTextArray: ( - p: Pick<TimelineProps, 'parseDetailsAsHTML' | 'fontSizes' | 'theme'> & { - cardTextClassName: string; - detailedText: string[]; - }, -) => ReactNode = ({ - fontSizes, - parseDetailsAsHTML, - theme, - detailedText, - cardTextClassName, -}) => { - return detailedText.map((text, index) => { - const props = parseDetailsAsHTML - ? { - dangerouslySetInnerHTML: { - __html: xss(text), - }, - } - : null; - return ( - <TimelineSubContent - key={index} - fontSize={fontSizes?.cardText} - className={cardTextClassName} - theme={theme} - {...props} - > - {parseDetailsAsHTML ? null : text} - </TimelineSubContent> - ); - }); -}; - -// Function to get the TextOrContent component -const getTextOrContent: ( - p: TextOrContentModel, -) => ForwardRefExoticComponent<TextOrContentModel> = ({ - timelineContent, - theme, - detailedText, - showMore, -}) => { - const TextOrContent = forwardRef<HTMLParagraphElement, TextOrContentModel>( - (prop, ref) => { - const isTextArray = Array.isArray(detailedText); - const { fontSizes, classNames, parseDetailsAsHTML } = - useContext(GlobalContext); - - const renderTimelineContent = () => { - if (timelineContent) { - return <div ref={ref}>{timelineContent}</div>; - } else { - let textContent = null; - if (isTextArray) { - textContent = renderTextArray({ - cardTextClassName: classNames?.cardText, - detailedText, - fontSizes, - parseDetailsAsHTML, - theme, - }); - } else { - textContent = parseDetailsAsHTML ? xss(detailedText) : detailedText; - } - - const textContentProps = - parseDetailsAsHTML && !isTextArray - ? { - dangerouslySetInnerHTML: { - __html: xss(textContent), - }, - } - : {}; - - return textContent ? ( - <TimelineContentDetails - className={showMore ? 'active' : ''} - ref={ref} - theme={theme} - {...textContentProps} - > - {parseDetailsAsHTML && !isTextArray ? null : textContent} - </TimelineContentDetails> - ) : null; - } - }; - - return renderTimelineContent(); - }, - ); - - TextOrContent.displayName = 'Text Or Content'; - - return TextOrContent; -}; - -export { getTextOrContent }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -1x -1x -1x -1x -1x -1x -1x -1x -41x - -1x -1x -1x -1x -1x -1x -1x -41x - - - - - - - - - - - - - -41x -41x -41x -41x -1x -1x -1x -41x - - -41x -41x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x - - - - - - - - - - - - - - - - - - - - - - - - -41x -1x -1x -41x - - - - - - -41x -41x - - - - -41x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -37x -1x -1x -41x -41x -41x -41x -41x -41x -41x - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -41x -41x -41x -41x -41x -41x -41x - - - - - - - - - -41x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - -1x -1x - - - - - - - - - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineProps } from '@models/TimelineModel'; -import styled, { css, keyframes } from 'styled-components'; -import { linearGradient } from '../timeline-card-media/timeline-card-media.styles'; -import { - reveal, - slideFromRight, - slideInFromLeft, - slideInFromTop, -} from './card-animations.styles'; - -type ContentT = Pick< - TimelineProps, - 'theme' | 'slideShow' | 'mode' | 'borderLessCards' ->; - -export const TimelineItemContentWrapper = styled.section< - { - $active?: boolean; - $borderLessCards?: TimelineProps['borderLessCards']; - $branchDir?: string; - $customContent?: boolean; - $highlight?: boolean; - $isNested?: boolean; - $maxWidth?: number; - $minHeight?: number; - $noMedia?: boolean; - $slideShow?: TimelineProps['slideShow']; - $slideShowActive?: boolean; - $slideShowType?: TimelineProps['slideShowType']; - $textOverlay?: boolean; - } & ContentT ->` - align-items: flex-start; - background: ${(p) => p.theme.cardBgColor}; - border-radius: 8px; - display: flex; - position: absolute; - ${({ borderLessCards }) => - !borderLessCards - ? `filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3))` - : 'none'}; - flex-direction: column; - justify-content: flex-start; - line-height: 1.5em; - margin: ${(p) => (p.mode === 'HORIZONTAL' ? '0 auto' : '')}; - max-width: ${(p) => p.$maxWidth}px; - // min-height: ${(p) => p.$minHeight}px; - ${(p) => - p.$customContent - ? `height: ${p.$minHeight}px;` - : `min-height: ${p.$minHeight}px;`} - position: relative; - text-align: left; - width: 98%; - z-index: 0; - - ${(p) => - p.$highlight - ? css` - &:hover { - filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.3)) brightness(1.05); - - &::before { - content: ''; - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; - z-index: -1; - border: 2px solid ${p.theme.primary}; - border-radius: 4px; - } - } - ` - : css``} - - ${(p) => - p.$isNested - ? css` - background: ${p.theme.nestedCardBgColor}; - box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1); - ` - : css``} - - height: ${(p) => (p.$textOverlay ? '0' : '')}; - - &:focus { - outline: 1px solid ${(p) => p.theme?.primary}; - } - - ${(p) => { - if (p.$slideShowActive && p.$active) { - if (p.$slideShowType === 'slide_in') { - return css` - animation: ${slideInFromTop} 0.5s ease-in-out; - `; - } else if ( - p.$slideShowType === 'slide_from_sides' && - p.$branchDir === 'left' - ) { - return css` - animation: ${slideInFromLeft} 0.5s ease-in-out; - `; - } else if ( - p.$slideShowType === 'slide_from_sides' && - p.$branchDir === 'right' - ) { - return css` - animation: ${slideFromRight} 0.5s ease-in-out; - `; - } else { - return css` - animation: ${reveal} 0.5s ease-in-out; - `; - } - } - }} - - ${(p) => { - if (p.$slideShowActive && p.$active) { - return css` - opacity: 1; - animation-timing-function: ease-in-out; - animation-duration: 0.5s; - `; - } - - if (p.$slideShowActive && !p.$active) { - return css` - opacity: 0; - `; - } - }} -`; - -export const TimelineCardHeader = styled.header` - width: 100%; - padding: 0.5rem 0.5rem 0 0.5rem; -`; - -export const CardSubTitle = styled.h2<{ - $fontSize?: string; - $padding?: boolean; - dir?: string; - theme?: Theme; -}>` - color: ${(p) => p.theme.cardSubtitleColor}; - font-size: ${(p) => p.$fontSize}; - font-weight: 600; - margin: 0; - text-align: left; - width: 97%; - padding: ${(p) => (p.$padding ? '0.5rem 0 0.5rem 0.5rem;' : '')}; -`; - -export const CardTitle = styled.h1<{ - $fontSize: string; - $padding?: boolean; - dir?: string; - theme: Theme; -}>` - color: ${(p) => p.theme.cardTitleColor}; - font-size: ${(p) => p.$fontSize}; - font-weight: 600; - margin: 0; - text-align: left; - width: 95%; - padding: ${(p) => (p.$padding ? '0.25rem 0 0.25rem 0.5rem;' : '')} &.active { - color: ${(p) => p.theme.primary}; - } -`; - -export const CardTitleAnchor = styled.a` - color: inherit; - - &:active { - color: inherit; - } -`; - -export const TimelineContentDetails = styled.p<{ theme?: Theme }>` - font-size: 0.85rem; - font-weight: 400; - margin: 0; - width: 100%; - color: ${(p) => p.theme.cardDetailsColor}; -`; - -export const TimelineSubContent = styled.span<{ - fontSize?: string; - theme?: Theme; -}>` - margin-bottom: 0.5rem; - display: block; - font-size: ${(p) => p.fontSize}; - color: ${(p) => p.theme.cardDetailsColor}; -`; - -export const TimelineContentDetailsWrapper = styled.div<{ - $borderLess?: boolean; - $cardHeight?: number | null; - $contentHeight?: number; - $customContent?: boolean; - $gradientColor?: string | null; - $showMore?: boolean; - $textOverlay?: boolean; - $useReadMore?: boolean; - branchDir?: string; - height?: number; - theme?: Theme; -}>` - align-items: center; - display: flex; - flex-direction: column; - margin: 0 auto; - margin-top: 0.5em; - margin-bottom: 0.5em; - position: relative; - ${({ $useReadMore, $customContent, $showMore, height = 0, $textOverlay }) => - $useReadMore && !$customContent && !$showMore && !$textOverlay - ? `max-height: ${height}px;` - : 'height: 100%'} - ${({ - $cardHeight = 0, - $contentHeight = 0, - height = 0, - $showMore, - $textOverlay, - }) => - $showMore && !$textOverlay - ? `max-height: ${($cardHeight || 0) + ($contentHeight || 0) - height}px;` - : ''} - overflow-x: hidden; - overflow-y: auto; - scrollbar-color: ${(p) => p.theme?.primary} default; - scrollbar-width: thin; - transition: max-height 0.25s ease-in-out; - width: ${(p) => - p.$borderLess ? 'calc(100% - 0.5rem)' : 'calc(95% - 0.5rem)'}; - padding: 0.25rem 0.25rem; - - ${(p) => (p.$customContent ? `height: 100%;` : '')} - - $${({ - height = 0, - $cardHeight = 0, - $contentHeight = 0, - $showMore, - $useReadMore, - }) => - $showMore && $useReadMore && $cardHeight - ? css` - animation: ${keyframes` - 0% { - max-height: ${height}px; - } - 100% { - max-height: ${$cardHeight + $contentHeight - height}px; - } - `} 0.25s ease-in-out; - ` - : ''} - &::-webkit-scrollbar { - width: 0.3em; - } - - &::-webkit-scrollbar-track { - box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2); - } - - &::-webkit-scrollbar-thumb { - background-color: ${(p) => p.theme?.primary}; - outline: 1px solid ${(p) => p.theme?.primary}; - } - - &.show-less { - scrollbar-width: none; - - &::-webkit-scrollbar { - width: 0; - } - overflow: hidden; - } - - --rc-gradient-color: ${(p) => p.$gradientColor}; - ${linearGradient} -`; - -export const ShowMore = styled.button<{ - show?: 'true' | 'false'; - theme?: Theme; -}>` - align-items: center; - align-self: flex-end; - border-radius: 4px; - cursor: pointer; - display: ${(p) => (p.show === 'true' ? 'flex' : 'none')}; - font-size: 0.75rem; - justify-self: flex-end; - margin-bottom: 0.5em; - margin-left: 0.5em; - margin-right: 0.5em; - margin-top: auto; - padding: 0.25em; - color: ${(p) => p.theme.primary}; - border: 0; - background: none; - - &:hover { - text-decoration: underline; - } -`; - -const slideAnimation = (start?: number, end?: number) => keyframes` - 0% { - width: ${start}px; - } - 100% { - width: ${end}px; - } -`; - -export const SlideShowProgressBar = styled.progress<{ - $color?: string; - $duration?: number; - $paused?: boolean; - $resuming?: boolean; - $startWidth?: number; -}>` - background: ${(p) => p.color}; - bottom: -0.75em; - display: block; - height: 4px; - left: 50%; - transform: translateX(-50%); - position: absolute; - border-radius: 2px; - border: 0; - - ${(p) => { - if (p.$paused) { - return css` - left: 50%; - transform: translateX(-50%); - `; - } - }} - - ${(p) => { - if (!p.$paused && p.$startWidth && p.$startWidth > 0) { - return css` - animation: ${slideAnimation(p.$startWidth, 0)} ${p.$duration}ms ease-in; - animation-play-state: running; - `; - } else { - return css` - animation-play-state: paused; - width: ${p.$startWidth}px; - `; - } - }} - - svg { - position: absolute; - left: 0; - top: 0; - width: 100%; - } -`; - -export const ChevronIconWrapper = styled.span<{ collapsed?: 'true' | 'false' }>` - align-items: center; - display: flex; - height: 1.25em; - justify-content: center; - margin-left: 0.2em; - margin-top: 0.2em; - width: 1.25em; - ${(p) => - p.collapsed === 'false' - ? ` - transform: rotate(90deg); - ` - : `transform: rotate(-90deg)`}; - - svg { - height: 100%; - width: 100%; - } -`; - -export const TriangleIconWrapper = styled.span<{ - dir?: string; - offset?: number; - theme?: Theme; -}>` - display: flex; - align-items: center; - justify-content: center; - width: 1.5rem; - height: 1.5rem; - position: absolute; - top: calc(50%); - background: ${(p) => p.theme.cardBgColor}; - transform: translateY(-50%) rotate(225deg); - z-index: -1; - - & svg { - width: 100%; - height: 100%; - fill: #fff; - } - - ${(p) => - p.dir === 'left' ? `right: ${p.offset}px;` : `left: ${p.offset}px;`}; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -32x -71x -71x -71x -71x -71x -71x -32x -71x -71x -71x -32x -32x -32x -32x -32x -71x -71x -71x -32x -6x -6x -71x -71x -71x -71x -82x -41x -41x -41x -82x - - -41x -41x -41x -41x -41x -41x -82x -71x -71x -71x -71x - - - - - - - - - - - - - - - - - - -71x -71x -71x -32x - - -71x -71x -71x -71x - - - - - - - - - - - - - -71x -71x -71x -71x - - - - - - - - - - - - - -71x -71x -71x -32x - - -32x -32x - - - -32x -32x -32x - - -32x -32x -32x -32x -71x -71x -71x -41x - - -71x -71x -71x -32x - - -71x -71x -71x -32x -71x -71x -71x -71x -71x -71x -32x -71x -71x -71x -71x -71x -71x -71x -71x - - - - - - - - - - - -71x -71x -71x -71x -71x -32x -32x -32x -32x -71x -71x -71x -71x -71x -32x -32x -28x -4x -32x -32x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -32x - -32x -32x -32x - - -71x -71x -71x - - - - -71x -71x -71x -32x - - - - - - -32x -71x -71x -71x -71x -71x -71x -71x -71x -32x -32x - -32x - -32x -71x -71x -71x -71x -32x -71x -71x -71x -32x -32x -32x -32x -32x -32x -71x -71x -71x -32x -32x -32x - - - - - - - - - - -32x -32x -32x -32x -32x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -64x -64x -64x -64x -64x -64x -64x -7x -71x -71x -71x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -22x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x - - - - - - - - - -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -71x -1x -1x -1x -1x -1x - | import { TimelineContentModel } from '@models/TimelineContentModel'; -import { MediaState } from '@models/TimelineMediaModel'; -import { hexToRGBA } from '@utils/index'; -import cls from 'classnames'; -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import Timeline from '../../timeline/timeline'; -import CardMedia from '../timeline-card-media/timeline-card-media'; -import { ContentFooter } from './content-footer'; -import { ContentHeader } from './content-header'; -import { DetailsText } from './details-text'; -import { getTextOrContent } from './text-or-content'; -import { TimelineItemContentWrapper } from './timeline-card-content.styles'; - -const TimelineCardContent: React.FunctionComponent<TimelineContentModel> = - React.memo( - ({ - active, - content, - detailedText, - id, - media, - onShowMore, - slideShowActive, - onElapsed, - theme, - title, - onClick, - customContent, - hasFocus, - flip, - branchDir, - url, - timelineContent, - items, - isNested, - nestedCardHeight, - }: TimelineContentModel) => { - const [showMore, setShowMore] = useState(false); - const detailsRef = useRef<HTMLDivElement | null>(null); - const containerRef = useRef<HTMLDivElement | null>(null); - const progressRef = useRef<HTMLProgressElement | null>(null); - - const containerWidth = useRef<number>(0); - const slideShowElapsed = useRef(0); - const timerRef = useRef(0); - const startTime = useRef<Date>(); - const [paused, setPaused] = useState(false); - const isFirstRender = useRef(true); - - const [remainInterval, setRemainInterval] = useState(0); - const [startWidth, setStartWidth] = useState(0); - const [textContentLarge, setTextContentLarge] = useState(false); - - const [cardActualHeight, setCardActualHeight] = useState(0); - const [detailsHeight, setDetailsHeight] = useState(0); - const [hasBeenActivated, setHasBeenActivated] = useState(false); - const [isResuming, setIsResuming] = useState(false); - - const { - mode, - cardHeight, - slideItemDuration = 2000, - useReadMore, - cardWidth, - borderLessCards, - disableAutoScrollOnClick, - classNames, - textOverlay, - slideShowType, - showProgressOnSlideshow, - disableInteraction, - highlightCardsOnHover, - } = useContext(GlobalContext); - - // If the media is a video, we don't show the progress bar. - // If the media is an image, we show the progress bar if the - // showProgressOnSlideshow flag is set. - const canShowProgressBar = useMemo(() => { - return active && slideShowActive && showProgressOnSlideshow; - }, [active, slideShowActive]); - - // This function returns a boolean value that indicates whether the user - // can see more information about the item. The detailed text is only - // available if the user has expanded the row. - const canShowMore = useMemo(() => { - return !!detailedText; - }, [detailedText]); - - useEffect(() => { - const detailsEle = detailsRef.current; - - if (detailsEle) { - detailsEle.scrollTop = 0; - } - }, [showMore]); - - useEffect(() => { - if (active) { - setHasBeenActivated(true); - } - }, [active]); - - const onContainerRef = useCallback( - (node: HTMLElement) => { - if (node === null) { - return; - } - const detailsEle = detailsRef.current; - if (!detailsEle) { - return; - } - const { scrollHeight, offsetTop } = detailsEle; - containerWidth.current = node.clientWidth; - setStartWidth(containerWidth.current); - setCardActualHeight(scrollHeight); - setDetailsHeight(detailsEle.offsetHeight); - setTextContentLarge(scrollHeight + offsetTop > node.clientHeight); - }, - [detailsRef.current], - ); - - const setupTimer = useCallback((interval: number) => { - if (!slideItemDuration) { - return; - } - - setRemainInterval(interval); - - startTime.current = new Date(); - - setPaused(false); - - timerRef.current = window.setTimeout(() => { - // clear the timer and move to the next card - window.clearTimeout(timerRef.current); - setPaused(true); - setStartWidth(0); - setRemainInterval(slideItemDuration); - id && onElapsed && onElapsed(id); - }, interval); - }, []); - - useEffect(() => { - if (timerRef.current && !slideShowActive) { - window.clearTimeout(timerRef.current); - } - }, [slideShowActive]); - - // pause the slide show - const tryHandlePauseSlideshow = useCallback(() => { - if (active && slideShowActive) { - window.clearTimeout(timerRef.current); - setPaused(true); - - if (startTime.current) { - const elapsed: any = +new Date() - +startTime.current; - slideShowElapsed.current = elapsed; - } - - if (progressRef.current) { - setStartWidth(progressRef.current.clientWidth); - } - } - }, [active, slideShowActive]); - - // resumes the slide show - const tryHandleResumeSlideshow = useCallback(() => { - if (active && slideShowActive) { - if (!slideItemDuration) { - return; - } - const remainingInterval = - slideItemDuration - slideShowElapsed.current; - - setPaused(false); - - if (remainingInterval > 0) { - setupTimer(remainingInterval); - } - } - }, [active, slideShowActive, slideItemDuration]); - - useEffect(() => { - if (!slideItemDuration) { - return; - } - // setup the timer - if (active && slideShowActive) { - setStartWidth(containerWidth.current); - setupTimer(slideItemDuration); - } - - // disabled autofocus on active - if (active && hasFocus) { - containerRef.current && containerRef.current.focus(); - } - - if (!slideShowActive) { - setHasBeenActivated(false); - } - }, [active, slideShowActive]); - - useEffect(() => { - if (hasFocus && active) { - containerRef.current && containerRef.current.focus(); - } - }, [hasFocus, active]); - - useEffect(() => { - if (!paused && !isFirstRender.current) { - setIsResuming(true); - } - }, [paused, startWidth]); - - useEffect(() => { - isFirstRender.current = false; - }, []); - - // This code is used to determine whether the read more button should be shown. - // It is only shown if the useReadMore prop is true, the detailedText is non-null, - // and the customContent prop is false. - const canShowReadMore = useMemo(() => { - return useReadMore && detailedText && !customContent; - }, []); - - // decorate the comments - // This function is triggered when the media state changes. If the slideshow is - // active, and the media state changes to paused, this function will call - // tryHandlePauseSlideshow(), which will pause the slideshow. - const handleMediaState = useCallback( - (state: MediaState) => { - if (!slideShowActive) { - return; - } - if (state.playing) { - tryHandlePauseSlideshow(); - } else if (state.paused) { - if (paused && id && onElapsed) { - onElapsed(id); - } - } - }, - [paused, slideShowActive], - ); - - const contentClass = useMemo( - () => - cls( - active ? 'timeline-card-content active' : 'timeline-card-content ', - classNames?.card, - ), - [active], - ); - - const contentDetailsClass = useMemo( - () => - cls( - !showMore && !customContent && useReadMore - ? 'show-less card-description' - : 'card-description', - classNames?.cardText, - ), - [showMore, customContent], - ); - - /** - * Calculate the minimum height of the card. If the card has a text overlay and - * media, the minimum height is equal to the card height. If the card is not - * nested, the minimum height is equal to the card height. If the card is nested, - * the minimum height is equal to the nested card height. - */ - const cardMinHeight = useMemo(() => { - if (textOverlay && media) { - return cardHeight; - } else if (!isNested) { - return cardHeight; - } else { - return nestedCardHeight; - } - }, []); - - const handleExpandDetails = useCallback(() => { - if ((active && paused) || !slideShowActive) { - setShowMore(!showMore); - onShowMore(); - } - }, [active, paused, slideShowActive, showMore]); - - const triangleDir = useMemo(() => { - if (flip) { - if (branchDir === 'right') { - return 'left'; - } else { - return 'right'; - } - } - return branchDir; - }, [branchDir, flip]); - - // Get the background color for the gradient, which is either the - // cardDetailsBackGround or nestedCardDetailsBackGround theme variable, - // based on whether the card is nested or not. If we are showing more - // content, the background color should be null, so that there is no - // gradient. - const gradientColor = useMemo(() => { - const bgToUse = !isNested - ? theme?.cardBgColor - : theme?.nestedCardDetailsBackGround; - return !showMore && textContentLarge - ? hexToRGBA(bgToUse || '#ffffff', 0.8) - : null; - }, [textContentLarge, showMore, theme?.cardDetailsBackGround, isNested]); - - // This code checks whether the textOverlay and items props are truthy. If so, then it returns false. Otherwise, it returns true. - const canShowDetailsText = useMemo(() => { - return !textOverlay && !items?.length; - }, [items?.length]); - - const TextOrContent = useMemo(() => { - return getTextOrContent({ - detailedText, - showMore, - theme, - timelineContent, - }); - }, [showMore, timelineContent, theme, detailedText]); - - const handlers = useMemo(() => { - if (!isNested && !disableInteraction) { - return { - onPointerDown: (ev: React.PointerEvent) => { - ev.stopPropagation(); - if ( - !slideShowActive && - onClick && - id && - !disableAutoScrollOnClick - ) { - onClick(id); - } - }, - onPointerEnter: tryHandlePauseSlideshow, - onPointerLeave: tryHandleResumeSlideshow, - }; - } - // }, [tryHandlePauseSlideshow, tryHandleResumeSlideshow]); - }, []); - - return ( - <TimelineItemContentWrapper - className={contentClass} - $minHeight={cardMinHeight} - $maxWidth={cardWidth} - mode={mode} - $noMedia={!media} - {...handlers} - ref={onContainerRef} - tabIndex={!isNested ? 0 : -1} - theme={theme} - $borderLessCards={borderLessCards} - $textOverlay={textOverlay} - $active={hasBeenActivated} - $slideShowType={slideShowType} - $slideShowActive={slideShowActive} - $branchDir={branchDir} - $isNested={isNested} - $highlight={highlightCardsOnHover} - data-testid="timeline-card-content" - $customContent={!!customContent} - > - {title && !textOverlay ? ( - <ContentHeader - title={title} - theme={theme} - url={url} - media={media} - content={content} - /> - ) : null} - - {/* render media video or image */} - {media && ( - <CardMedia - active={active} - cardHeight={cardHeight} - content={content} - hideMedia={showMore} - id={id} - media={media} - onMediaStateChange={handleMediaState} - slideshowActive={slideShowActive} - theme={theme} - title={title} - url={url} - startWidth={startWidth} - detailsText={TextOrContent} - paused={paused} - remainInterval={remainInterval} - showProgressBar={canShowProgressBar} - triangleDir={triangleDir} - resuming={isResuming} - progressRef={progressRef} - /> - )} - - {canShowDetailsText ? ( - <DetailsText - showMore={showMore} - gradientColor={gradientColor} - detailedText={detailedText} - customContent={customContent} - timelineContent={timelineContent} - contentDetailsClass={contentDetailsClass} - cardActualHeight={cardActualHeight} - detailsHeight={detailsHeight} - ref={detailsRef} - /> - ) : ( - <Timeline - items={items} - mode={'VERTICAL'} - enableOutline={false} - hideControls - nestedCardHeight={nestedCardHeight} - isChild - /> - )} - - {(!textOverlay || !media) && ( - <ContentFooter - theme={theme} - progressRef={progressRef} - startWidth={startWidth} - textContentIsLarge={textContentLarge} - remainInterval={remainInterval} - paused={paused} - triangleDir={triangleDir} - showProgressBar={canShowProgressBar} - showReadMore={canShowReadMore} - onExpand={handleExpandDetails} - canShow={canShowMore} - showMore={showMore} - isNested={isNested} - isResuming={isResuming} - /> - )} - </TimelineItemContentWrapper> - ); - }, - ); - -TimelineCardContent.displayName = 'TimelineCardContent'; - -export default TimelineCardContent; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-card-media-buttons.tsx | -
-
- |
- 100% | -48/48 | -100% | -0/0 | -100% | -0/0 | -100% | -48/48 | -
timeline-card-media.styles.ts | -
-
- |
- 79.41% | -189/238 | -47.22% | -17/36 | -76.47% | -13/17 | -79.41% | -189/238 | -
timeline-card-media.tsx | -
-
- |
- 81.46% | -334/410 | -59.52% | -25/42 | -25% | -1/4 | -81.46% | -334/410 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled, { css } from 'styled-components'; - -const Button = css` - align-items: center; - background: none; - // background: rgba(0, 0, 0, 0.1); - border-radius: 50%; - border: none; - cursor: pointer; - display: flex; - height: 1.5rem; - justify-content: center; - padding: 0; - width: 1.5rem; - margin: 0 0.25rem; - background: ${(p) => p.theme?.primary}; - color: #fff; - - svg { - width: 70%; - height: 70%; - } -`; - -export const ExpandButton = styled.button<{ - // expandFull?: boolean; - theme: Theme; -}>` - ${Button} -`; - -export const ShowHideTextButton = styled.button<{ - showText?: boolean; - theme: Theme; -}>` - ${Button} -`; - -export const ButtonWrapper = styled.ul` - display: flex; - flex-direction: row; - justify-content: flex-end; - list-style: none; - margin: 0; - padding: 0; - margin-left: auto; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x - - - -10x -10x - - - -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x -1x -10x - - - - - - -10x -10x - - - - - - -10x -10x - - - - - - -10x -1x -1x -10x - - - - - - - - - - -10x -10x -1x -1x -1x -10x - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - - -1x -1x - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled, { css } from 'styled-components'; -import { ScrollBar } from '../../common/styles'; - -export const linearGradient = css` - &::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 2rem; - background: linear-gradient( - 0deg, - var(--rc-gradient-color) 0%, - rgba(255, 255, 255, 0) 100% - ); - } -`; - -export const MediaWrapper = styled.div<{ - $active?: boolean; - $cardHeight?: number; - $slideShowActive?: boolean; - $textOverlay?: boolean; - align?: 'left' | 'right' | 'center'; - dir?: string; - mode?: TimelineMode; - theme?: Theme; -}>` - align-items: flex-start; - align-self: center; - background: ${(p) => (!p.$textOverlay ? p.theme?.cardMediaBgColor : 'none')}; - border-radius: 4px; - flex-direction: row; - height: ${(p) => (p.$textOverlay ? 'calc(100% - 1em)' : '0')}; - padding: 0.5em; - // pointer-events: ${(p) => (!p.$active && p.$slideShowActive ? 'none' : '')}; - position: relative; - text-align: ${(p) => p.align}; - width: calc(100% - 1em); - - ${(p) => (p.$cardHeight ? `min-height: ${p.$cardHeight}px;` : '')}; - ${(p) => { - if (p.mode === 'HORIZONTAL') { - return ` - justify-content: flex-start; - `; - } else { - if (p.dir === 'left') { - return ` - justify-content: flex-start; - `; - } else { - return ` - justify-content: flex-end; - `; - } - } - }} -`; - -export const CardImage = styled.img<{ - $enableBorderRadius?: boolean; - $visible?: boolean; - dir?: string; - fit?: string; - mode?: TimelineMode; -}>` - flex: 4; - justify-self: center; - margin-left: auto; - margin-right: auto; - height: 100%; - width: 100%; - object-fit: ${(p) => p.fit || 'cover'}; - object-position: center; - visibility: ${(p) => (p.$visible ? 'visible' : 'hidden')}; - border-radius: ${(p) => (p.$enableBorderRadius ? '6px' : '0')}; -`; - -export const CardVideo = styled.video<{ height?: number }>` - max-width: 100%; - max-height: 100%; - margin-left: auto; - margin-right: auto; -`; - -export const MediaDetailsWrapper = styled.div<{ - $absolutePosition?: boolean; - $borderLessCard?: boolean; - $expandFull?: boolean; - $expandable?: boolean; - $gradientColor?: string | null; - $showText?: boolean; - $textInMedia?: boolean; - mode?: TimelineMode; - theme?: Theme; -}>` - bottom: 0; - left: 0; - right: 0; - margin-right: auto; - width: ${(p) => { - switch (p.mode) { - case 'HORIZONTAL': - case 'VERTICAL': - case 'VERTICAL_ALTERNATING': - return `calc(90% - 0rem)`; - } - }}; - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - ${(p) => { - if (p.$textInMedia && p.$expandFull) { - return css` - height: 100%; - width: 100%; - border: 0; - `; - } - - if (!p.$showText) { - return css` - height: 15%; - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); - border-radius: 10px; - `; - } - - if (p.$textInMedia && p.$expandable) { - return css` - box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); - border-radius: 10px; - height: 50%; - `; - } - }} - position: ${(p) => (p.$absolutePosition ? 'absolute' : 'relative')}; - ${(p) => - p.$absolutePosition - ? ` - left: 50%; - bottom: ${p.$expandFull ? '0%' : ' 5%'}; - transform: translateX(-50%); - background: ${ - p.$showText ? p.theme?.cardDetailsBackGround : p.theme?.cardBgColor - }; - // backdrop-filter: blur(1px); - padding: 0.25rem; - ${p.$showText ? `overflow: auto;` : `overflow: hidden;`} - transition: height 0.25s ease-out, width 0.25s ease-out, bottom 0.25s ease-out, background 0.25s ease-out; - ` - : ``} - - ${({ $borderLessCard }) => - $borderLessCard - ? `border-radius: 6px; box-shadow: 0 0 10px 0 rgba(0,0,0,0.2);` - : ``} - --rc-gradient-color: ${(p) => p.$gradientColor}; - ${(p) => (p.$gradientColor ? linearGradient : null)} -`; - -export const ErrorMessage = styled.span` - color: #a3a3a3; - left: 50%; - position: absolute; - text-align: center; - top: 50%; - transform: translateY(-50%) translateX(-50%); -`; - -export const IFrameVideo = styled.iframe` - position: relative; - height: 100%; - width: 100%; -`; - -export const DetailsTextWrapper = styled.div<{ - $expandFull?: boolean; - $show?: boolean; - background: string; - theme?: Theme; -}>` - align-self: center; - display: flex; - transition: height 0.5s ease; - width: calc(100%); - background: ${(p) => p.background}; - color: ${(p) => p.theme?.cardDetailsColor}; - padding: 0.5rem; - border-bottom-left-radius: 8px; - border-bottom-right-radius: 8px; - position: relative; - align-items: flex-start; - justify-content: center; - - ${ScrollBar} - - ${(p) => { - if (p.$expandFull) { - return ` - overflow: auto; - `; - } else { - return ` - overflow: hidden; - `; - } - }} - - ${(p) => - p.$show - ? ` - height: 100%;` - : ` - height: 0; - `} - - ${(p) => !p.$expandFull && linearGradient} -`; - -export const CardMediaHeader = styled.div` - padding: 0.5rem 0 0.5rem 0.5rem; - display: flex; - align-items: center; - justify-content: center; -`; - -export const ImageWrapper = styled.div<{ height?: number }>` - width: 100%; - height: 100%; - overflow: hidden; - border-radius: 6px; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - -10x -10x -6x -6x -10x -4x -4x -4x -10x -10x -10x -10x - -10x -10x -10x -10x - - - - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - -10x -10x - - - - - -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - -10x -10x -10x -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - - - - - - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -1x - - - - -10x -10x -10x -9x -9x - - -10x -10x -10x - - - - - - - - -10x -10x -10x - - - - - - - -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x - | import { CardMediaModel } from '@models/TimelineMediaModel'; -import { hexToRGBA } from '@utils/index'; -import cls from 'classnames'; -import React, { - FunctionComponent, - memo, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { - DetailsTextMemo, - ExpandButtonMemo, - ShowOrHideTextButtonMemo, - SubTitleMemo, - TitleMemo, -} from '../memoized'; -import { - SlideShowProgressBar, - TriangleIconWrapper, -} from '../timeline-card-content/timeline-card-content.styles'; -import { ButtonWrapper } from './timeline-card-media-buttons'; -import { - CardImage, - CardMediaHeader, - CardVideo, - ErrorMessage, - IFrameVideo, - ImageWrapper, - MediaDetailsWrapper, - MediaWrapper, -} from './timeline-card-media.styles'; - -interface ErrorMessageModel { - message: string; -} - -const CardMedia: React.FunctionComponent<CardMediaModel> = ({ - active, - id, - onMediaStateChange, - title, - content, - media, - slideshowActive, - url, - detailsText, - showProgressBar, - remainInterval, - startWidth, - paused, - triangleDir, - resuming, - progressRef, -}: CardMediaModel) => { - const videoRef = useRef<HTMLVideoElement>(null); - const [loadFailed, setLoadFailed] = useState(false); - const moreRef = useRef(null); - const [detailsHeight, setDetailsHeight] = useState(0); - const [expandDetails, setExpandDetails] = useState(false); - const [showText, setShowText] = useState(true); - const [mediaLoaded, setMediaLoaded] = useState(false); - - const { - mode, - fontSizes, - classNames, - mediaHeight, - borderLessCards, - textOverlay, - theme, - cardHeight, - mediaSettings, - } = useContext(GlobalContext); - - useEffect(() => { - if (!videoRef) { - return; - } - - if (active) { - // play the video when active - videoRef.current && videoRef.current.play(); - } else { - // pause the video when not active - videoRef.current && videoRef.current.pause(); - } - }, [active]); - - // This function will be invoked when the user has finished loading media - const handleMediaLoaded = useCallback(() => { - setMediaLoaded(true); - }, []); - - // This code creates a function to handle errors when loading the video. - const handleError = useCallback(() => { - // create a function to handle errors - setLoadFailed(true); // set the loadFailed variable to true - onMediaStateChange({ - // call the onMediaStateChange function - id, - paused: false, - playing: false, - }); - }, [onMediaStateChange, id]); // add the onMediaStateChange and id variables as dependencies to the function - - const ErrorMessageMem: FunctionComponent<ErrorMessageModel> = memo( - ({ message }: ErrorMessageModel) => <ErrorMessage>{message}</ErrorMessage>, - ); - - // Checks if the media source url is a YouTube video. - // Returns a boolean. - const isYouTube = useMemo( - () => - /^(https?\:\/\/)?(www\.youtube\.com|youtu\.?be)\/.+$/.test( - media.source.url, - ), - [], - ); - - /** - * @function IFrameVideo - * @description - * The IFrameVideo component is used to display an iframe with a YouTube video. - * @returns {IFrameVideo} - Returns the iframe with the YouTube video. - */ - const IFrameYouTube = useMemo(() => { - // Create an iframe with the YouTube video - const iframe = ( - <IFrameVideo - frameBorder="0" - allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" - allowFullScreen - src={`${media.source.url}${ - active ? '?autoplay=1&enablejsapi=1' : '?enablejsapi=1' - }`} - title={media.name} - data-testid="timeline-card-content-video" - /> - ); - - // When the YouTube video is active, return the iframe - return iframe; - }, [active]); - - const Video = useMemo(() => { - return ( - <CardVideo - controls - autoPlay={active} - ref={videoRef} - onLoadedData={handleMediaLoaded} - data-testid="rc-video" - onPlay={() => - onMediaStateChange({ - id, - paused: false, - playing: true, - }) - } - onPause={() => - onMediaStateChange({ - id, - paused: true, - playing: false, - }) - } - onEnded={() => - onMediaStateChange({ - id, - paused: false, - playing: false, - }) - } - onError={handleError} - > - <source src={media.source.url}></source> - </CardVideo> - ); - }, [active]); - - const Image = useMemo(() => { - return ( - <CardImage - src={media.source.url} - mode={mode} - onLoad={handleMediaLoaded} - onError={handleError} - $visible={mediaLoaded} - alt={media.name} - loading={'lazy'} - $enableBorderRadius={borderLessCards} - fit={mediaSettings?.imageFit} - data-testid="timeline-card-content-image" - /> - ); - }, [mediaLoaded, borderLessCards]); - - ErrorMessageMem.displayName = 'Error Message'; - - // This code calculates the height of the Details component and passes it to - // the setDetailsHeight function. - const onDetailsTextRef = useCallback((height?: number) => { - if (height) { - setDetailsHeight(height); - } - }, []); - - /* Toggle the expand details state on pointer or keyboard event */ - const toggleExpand = useCallback( - (ev: React.PointerEvent | React.KeyboardEvent) => { - // ev.preventDefault(); - // ev.stopPropagation(); - setExpandDetails((prev) => !prev); - setShowText(true); - }, - [], - ); - - // This function is used to toggle the text between hidden and visible. - // It is used to show the text of the article excerpt when the user - // clicks on the "show more" button. - const toggleText = useCallback( - (ev: React.PointerEvent | React.KeyboardEvent) => { - // ev.preventDefault(); - // ev.stopPropagation(); - setExpandDetails(false); - setShowText((prev) => !prev); - }, - [], - ); - - // checks if the arrow should be shown - const canShowArrow = useMemo( - () => - (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && textOverlay, - [], - ); - - // checks if we can display the detailed text - const canShowTextMemo = useMemo( - () => showText && !!detailsText, - [showText, detailsText], - ); - - // checks if the text content should be shown - const canShowTextContent = useMemo( - () => title || content || detailsText, - [title, content, detailsText], - ); - - const canExpand = useMemo( - () => textOverlay && !!detailsText, - [content, detailsText], - ); - - const gradientColor = useMemo( - () => hexToRGBA(theme?.cardDetailsBackGround || '', 0.8), - [theme?.cardDetailsBackGround], - ); - - const canShowGradient = useMemo( - () => !expandDetails && showText && textOverlay, - [expandDetails, showText], - ); - - const getCardHeight = useMemo(() => { - if (textOverlay) { - return cardHeight; - } else { - return mediaHeight; - } - }, []); - - const TextContent = useMemo(() => { - return ( - <MediaDetailsWrapper - mode={mode} - $absolutePosition={textOverlay} - $textInMedia={textOverlay} - ref={moreRef} - theme={theme} - $expandFull={expandDetails} - $showText={showText} - $expandable={canExpand} - $gradientColor={canShowGradient ? gradientColor : null} - > - <CardMediaHeader> - <TitleMemo - title={title} - theme={theme} - active={active} - url={url} - fontSize={fontSizes?.cardTitle} - classString={classNames?.cardTitle} - /> - {canExpand ? ( - <ButtonWrapper> - <ShowOrHideTextButtonMemo - onToggle={toggleText} - show={showText} - textOverlay - theme={theme} - /> - <ExpandButtonMemo - theme={theme} - expanded={expandDetails} - onExpand={toggleExpand} - textOverlay - /> - </ButtonWrapper> - ) : null} - </CardMediaHeader> - {showText && ( - <SubTitleMemo - content={content} - fontSize={fontSizes?.cardSubtitle} - classString={classNames?.cardSubTitle} - padding - theme={theme} - /> - )} - {canShowTextMemo ? ( - <> - <DetailsTextMemo - theme={theme} - show={showText} - expand={expandDetails} - text={detailsText} - onRender={onDetailsTextRef} - textOverlay={textOverlay} - /> - </> - ) : null} - </MediaDetailsWrapper> - ); - }, [ - showText, - expandDetails, - canShowTextMemo, - gradientColor, - title, - JSON.stringify(theme), - ]); - - const canShowProgressBar = useMemo( - () => showProgressBar && textOverlay, - [showProgressBar, textOverlay], - ); - - return ( - <> - <MediaWrapper - theme={theme} - $active={active} - mode={mode} - $slideShowActive={slideshowActive} - className={cls('card-media-wrapper', classNames?.cardMedia)} - $cardHeight={getCardHeight} - align={mediaSettings?.align} - $textOverlay={textOverlay} - > - {media.type === 'VIDEO' && - !isYouTube && - (!loadFailed ? ( - Video - ) : ( - <ErrorMessageMem message="Failed to load the video" /> - ))} - {media.type === 'VIDEO' && isYouTube && IFrameYouTube} - {media.type === 'IMAGE' && - (!loadFailed ? ( - <ImageWrapper height={mediaHeight}>{Image}</ImageWrapper> - ) : ( - <ErrorMessageMem message="Failed to load the image." /> - ))} - - {canShowProgressBar ? ( - <SlideShowProgressBar - color={theme?.primary} - $duration={remainInterval} - $paused={paused} - ref={progressRef} - $startWidth={startWidth} - $resuming={resuming} - ></SlideShowProgressBar> - ) : null} - - {canShowArrow ? ( - <TriangleIconWrapper - dir={triangleDir} - theme={theme} - offset={-15} - role="img" - data-testid="arrow-icon" - ></TriangleIconWrapper> - ) : null} - </MediaWrapper> - {canShowTextContent ? TextContent : null} - </> - ); -}; - -CardMedia.displayName = 'Card Media'; - -export default CardMedia; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-horizontal-card.styles.ts | -
-
- |
- 97.33% | -146/150 | -63.63% | -14/22 | -100% | -9/9 | -97.33% | -146/150 | -
timeline-horizontal-card.tsx | -
-
- |
- 96.42% | -189/196 | -86.36% | -19/22 | -66.66% | -2/3 | -96.42% | -189/196 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -58x -58x -58x - - - - -58x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled, { keyframes } from 'styled-components'; - -export const Wrapper = styled.div` - align-items: center; - border: 1px solid transparent; - display: flex; - justify-content: center; - position: relative; - width: 100%; - height: 100%; - - &.vertical { - justify-content: flex-start; - } -`; - -export const Item = styled.div``; - -const show = keyframes` - from { - opacity: 0; - } - to { - opacity: 1; - } -`; - -export const ShapeWrapper = styled.div` - /* height: 100%; */ - align-items: center; - display: flex; - flex-direction: column; - justify-content: center; - width: 5em; -`; - -type ShapeModel = { - $timelinePointShape?: 'circle' | 'square' | 'diamond'; - dimension?: number; - theme?: Theme; -}; - -const ShapeBorderStyle = (p: Pick<ShapeModel, '$timelinePointShape'>) => { - if (p.$timelinePointShape === 'circle') { - return 'border-radius: 50%;'; - } else if (p.$timelinePointShape === 'square') { - return 'border-radius: 2px;'; - } else if (p.$timelinePointShape === 'diamond') { - return `border-radius: 0;`; - } -}; - -export const Shape = styled.div<ShapeModel>` - ${ShapeBorderStyle} - cursor: pointer; - height: ${(p) => p.dimension}px; - width: ${(p) => p.dimension}px; - transform: ${(p) => - p.$timelinePointShape === 'diamond' ? 'rotate(45deg)' : ''}; - - &.active { - &.using-icon { - /* transform: scale(1.75); */ - } - &:not(.using-icon) { - transform: ${(p) => - p.$timelinePointShape === 'diamond' ? 'rotate(45deg)' : ''}; - } - - &::after { - ${ShapeBorderStyle} - content: ''; - display: block; - height: ${(p) => (p.dimension ? Math.round(p.dimension * 0.75) : 20)}px; - width: ${(p) => (p.dimension ? Math.round(p.dimension * 0.75) : 20)}px; - left: 50%; - position: absolute; - top: 50%; - transform: translateY(-50%) translateX(-50%); - z-index: -1; - } - } - - &:not(.using-icon) { - background: ${(p: ShapeModel) => p.theme?.primary}; - - &.active { - background: ${(p: ShapeModel) => p.theme?.secondary}; - border: ${(p) => (p.dimension ? Math.round(p.dimension * 0.2) : '3')}px - solid ${(p: ShapeModel) => p.theme?.primary}; - } - - &.in-active { - } - } - - &.using-icon { - background: ${(p) => p.theme?.iconBackgroundColor}; - display: flex; - align-items: center; - justify-content: center; - - img { - max-width: 90%; - max-height: 90%; - } - } -`; - -export const TimelineTitleContainer = styled.div` - display: flex; - align-items: center; - justify-content: flex-start; - - &.vertical { - margin-bottom: 1em; - } - - &.horizontal { - position: absolute; - top: 0; - } -`; - -export const TimelineContentContainer = styled.div<{ - $active?: boolean; - $cardWidth?: number; - $highlight?: boolean; - position?: string; - theme?: Theme; -}>` - align-items: flex-start; - animation: ${show} 0.25s ease-in; - - outline: 2px solid - ${(p) => (p.$highlight && p.$active ? p.theme?.primary : 'transparent')}; - - margin: 1rem; - - &.horizontal { - min-width: ${(p) => p.$cardWidth}px; - } - - &.vertical { - width: calc(100% - 5em); - margin-left: auto; - flex-direction: column; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x - - - - -9x -9x -9x -6x -6x -6x -6x -6x -6x -6x -6x - - - -6x -6x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -6x -6x -6x -6x -6x -6x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -1x -1x - | import { TimelineCardModel } from '@models/TimelineItemModel'; -import cls from 'classnames'; -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, -} from 'react'; -import ReactDOM from 'react-dom'; -import { GlobalContext } from '../../GlobalContext'; -import TimelineCardContent from '../timeline-card-content/timeline-card-content'; -import TimelineItemTitle from '../timeline-item-title/timeline-card-title'; -import { - Shape, - ShapeWrapper, - TimelineContentContainer, - TimelineTitleContainer, - Wrapper, -} from './timeline-horizontal-card.styles'; - -const TimelineCard: React.FunctionComponent<TimelineCardModel> = ({ - active, - autoScroll, - cardDetailedText, - cardSubtitle, - cardTitle, - url, - id, - media, - onClick, - onElapsed, - slideShowRunning, - title, - wrapperId, - customContent, - hasFocus, - iconChild, - timelineContent, - cardWidth, - isNested, - nestedCardHeight, - items, -}: TimelineCardModel) => { - const circleRef = useRef<HTMLDivElement>(null); - const wrapperRef = useRef<HTMLDivElement>(null); - const contentRef = useRef<HTMLDivElement>(null); - - const { - mode, - cardPositionHorizontal: position, - timelinePointDimension, - disableClickOnCircle, - cardLess, - showAllCardsHorizontal, - classNames, - theme, - timelinePointShape, - disableInteraction, - } = useContext(GlobalContext); - - const handleClick = () => { - if (!disableClickOnCircle && onClick && !slideShowRunning) { - onClick(id); - } - }; - - useEffect(() => { - if (active) { - const circle = circleRef.current; - const wrapper = wrapperRef.current; - - if (circle && wrapper) { - const circleOffsetLeft = circle.offsetLeft; - const wrapperOffsetLeft = wrapper.offsetLeft; - - autoScroll?.({ - pointOffset: circleOffsetLeft + wrapperOffsetLeft, - pointWidth: circle.clientWidth, - }); - } - } - }, [active, autoScroll, mode]); - - const handleOnShowMore = useCallback(() => {}, []); - - const modeLower = useMemo(() => mode?.toLowerCase(), [mode]); - - const containerClass = useMemo( - () => - cls( - 'timeline-horz-card-wrapper', - modeLower, - position === 'TOP' ? 'bottom' : 'top', - showAllCardsHorizontal ? 'show-all' : '', - ), - [mode, position], - ); - - const titleClass = useMemo(() => cls(modeLower, position), []); - - const circleClass = useMemo( - () => - cls( - 'timeline-circle', - { 'using-icon': !!iconChild }, - modeLower, - active ? 'active' : 'in-active', - ), - [active], - ); - - const Content = useMemo(() => { - return ( - <TimelineContentContainer - className={containerClass} - ref={contentRef} - id={`timeline-card-${id}`} - theme={theme} - $active={active && !disableInteraction} - $highlight={showAllCardsHorizontal} - tabIndex={0} - $cardWidth={cardWidth} - > - <TimelineCardContent - content={cardSubtitle} - active={active} - title={cardTitle} - url={url} - detailedText={cardDetailedText} - onShowMore={handleOnShowMore} - theme={theme} - slideShowActive={slideShowRunning} - media={media} - onElapsed={onElapsed} - id={id} - customContent={customContent} - hasFocus={hasFocus} - onClick={onClick} - timelineContent={timelineContent} - isNested={isNested} - nestedCardHeight={nestedCardHeight} - items={items} - /> - </TimelineContentContainer> - ); - }, [active, slideShowRunning, JSON.stringify(theme)]); - - const showTimelineContent = () => { - const ele = document.getElementById(wrapperId); - - if (ele) { - return ReactDOM.createPortal(Content, ele); - } - }; - - const canShowTimelineContent = useMemo( - () => (active && !cardLess) || showAllCardsHorizontal, - [active, cardLess, showAllCardsHorizontal], - ); - - return ( - <Wrapper ref={wrapperRef} className={modeLower} data-testid="timeline-item"> - {canShowTimelineContent && showTimelineContent()} - - <ShapeWrapper> - <Shape - className={circleClass} - onClick={handleClick} - ref={circleRef} - data-testid="timeline-circle" - theme={theme} - aria-label={title} - dimension={timelinePointDimension} - $timelinePointShape={timelinePointShape} - > - {iconChild ? iconChild : null} - </Shape> - </ShapeWrapper> - - <TimelineTitleContainer - className={titleClass} - data-testid="timeline-title" - > - <TimelineItemTitle - title={title} - active={active && !disableInteraction} - theme={theme} - classString={classNames?.title} - /> - </TimelineTitleContainer> - </Wrapper> - ); -}; - -export default TimelineCard; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-control.styles.ts | -
-
- |
- 100% | -89/89 | -100% | -10/10 | -100% | -2/2 | -100% | -89/89 | -
timeline-control.tsx | -
-
- |
- 97.35% | -221/227 | -40.54% | -15/37 | -100% | -1/1 | -97.35% | -221/227 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -60x -16x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -60x -40x -40x -60x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled from 'styled-components'; - -export const TimelineNavWrapper = styled.ul<{ theme?: Theme }>` - border-radius: 25px; - display: flex; - list-style: none; - padding: 0.25em 0.25em; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); - background: ${(p) => p.theme.toolbarBtnBgColor}; -`; - -export const TimelineNavItem = styled.li<{ $disable?: boolean }>` - padding: 0.1em; - display: flex; - align-items: center; - justify-content: center; - ${(p) => - p.$disable - ? 'pointer-events: none; filter: opacity(0.5) grayscale(95%);' - : ''}; -`; - -export const TimelineNavButton = styled.button<{ - mode?: TimelineMode; - rotate?: 'TRUE' | 'FALSE'; - theme?: Theme; -}>` - align-items: center; - background: ${(p) => p.theme.primary}; - filter: brightness(1.25); - border-radius: 50%; - border: 0; - color: #fff; - cursor: pointer; - display: flex; - height: 24px; - justify-content: center; - margin: 0 0.2em; - padding: 0; - transition: all 0.1s ease-in; - width: 24px; - - transform: ${(p) => { - if (p.rotate === 'TRUE') { - return `rotate(90deg)`; - } - }}; - - &:active { - filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.25)); - transform: ${(p) => (p.rotate === 'TRUE' ? 'rotate(90deg)' : '')} scale(0.9); - } - - svg { - width: 65%; - height: 65%; - } -`; - -export const TimelineControlContainer = styled.div` - align-items: center; - display: flex; - justify-content: center; -`; - -export const ControlButton = styled.button<{ theme?: Theme }>` - align-items: center; - background: ${(p) => p.theme.primary}; - border-radius: 50%; - cursor: pointer; - display: flex; - height: 3em; - justify-content: center; - margin-left: 0.5em; - width: 3em; - outline: 0; - color: #fff; - - svg { - width: 80%; - height: 80%; - } -`; - -export const MediaToggle = styled(ControlButton)``; - -export const ReplayWrapper = styled(ControlButton)``; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - - - - - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -10x -10x -10x -10x -1x -1x -1x -1x - | import { TimelineControlModel } from '@models/TimelineControlModel'; -import cls from 'classnames'; -import React, { useCallback, useContext, useMemo } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { MoonIcon, StopIcon, SunIcon } from '../../icons'; -import ChevronLeft from '../../icons/chev-left'; -import ChevronRightIcon from '../../icons/chev-right'; -import ChevronsLeftIcon from '../../icons/chevs-left'; -import ChevronsRightIcon from '../../icons/chevs-right'; -import ReplayIcon from '../../icons/replay-icon'; -import { - TimelineControlContainer, - TimelineNavButton, - TimelineNavItem, - TimelineNavWrapper, -} from './timeline-control.styles'; - -/** - * TimelineControl component - * Provides navigation controls for a timeline, including next, previous, first, last, and slideshow buttons. - * Optionally supports flipping the layout and dark mode toggle. - * - * @property {function} onNext - Function to go to the next item. - * @property {function} onPrevious - Function to go to the previous item. - * @property {function} onFirst - Function to jump to the first item. - * @property {function} onLast - Function to jump to the last item. - * @property {boolean} disableLeft - Whether to disable the left navigation buttons. - * @property {boolean} disableRight - Whether to disable the right navigation buttons. - * @property {boolean} slideShowRunning - Whether the slideshow is currently running. - * @property {function} onReplay - Function to restart the slideshow. - * @property {boolean} slideShowEnabled - Whether the slideshow feature is enabled. - * @property {function} onToggleDarkMode - Function to toggle dark mode (if enabled). - * @property {boolean} isDark - Whether dark mode is currently active. - * @property {function} onPaused - Function to pause the slideshow (if running). - * @returns {JSX.Element} The TimelineControl component. - */ -const TimelineControl: React.FunctionComponent<TimelineControlModel> = ({ - onNext, - onPrevious, - onFirst, - onLast, - disableLeft, - disableRight, - slideShowRunning, - onReplay, - slideShowEnabled, - onToggleDarkMode, - isDark, - onPaused, -}: TimelineControlModel) => { - const { - mode, - flipLayout, - theme, - buttonTexts, - classNames, - enableDarkToggle, - disableInteraction, - } = useContext(GlobalContext); - - const rotate = useMemo(() => mode !== 'HORIZONTAL', [mode]); - - const flippedHorizontally = useMemo( - () => flipLayout && mode === 'HORIZONTAL', - [], - ); - - const canDisableLeft = useMemo( - () => disableLeft || slideShowRunning, - [disableLeft, slideShowRunning], - ); - - const canDisableRight = useMemo( - () => disableRight || slideShowRunning, - [disableRight, slideShowRunning], - ); - - const handlePlayOrPause = useCallback(() => { - if (slideShowRunning) { - onPaused?.(); - } else { - onReplay?.(); - } - }, [slideShowRunning]); - - const previousTitle = useMemo( - () => (flipLayout ? buttonTexts?.next : buttonTexts?.previous), - [flipLayout], - ); - - const nextTitle = useMemo( - () => (flipLayout ? buttonTexts?.previous : buttonTexts?.next), - [flipLayout], - ); - - const playOrPauseTile = useMemo( - () => (slideShowRunning ? buttonTexts?.stop : buttonTexts?.play), - [slideShowRunning], - ); - - const jumpToLastTitle = useMemo( - () => (flipLayout ? buttonTexts?.first : buttonTexts?.last), - [flipLayout], - ); - - const jumpToFirstTitle = useMemo( - () => (flipLayout ? buttonTexts?.last : buttonTexts?.first), - [flipLayout], - ); - - return ( - <TimelineControlContainer> - <TimelineNavWrapper - className={cls('timeline-controls', classNames?.controls)} - theme={theme} - > - {/* jump to first */} - {disableInteraction ? null : ( - <> - <TimelineNavItem $disable={canDisableLeft}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onLast : onFirst} - title={jumpToFirstTitle} - aria-label={jumpToFirstTitle} - aria-disabled={disableLeft} - aria-controls="timeline-main-wrapper" - tabIndex={!disableLeft ? 0 : -1} - rotate={rotate ? 'TRUE' : 'FALSE'} - > - <ChevronsLeftIcon /> - </TimelineNavButton> - </TimelineNavItem> - - {/* previous */} - <TimelineNavItem $disable={canDisableLeft}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onNext : onPrevious} - title={previousTitle} - aria-label={previousTitle} - aria-disabled={disableLeft} - aria-controls="timeline-main-wrapper" - tabIndex={!disableLeft ? 0 : -1} - rotate={rotate ? 'TRUE' : 'FALSE'} - > - <ChevronLeft /> - </TimelineNavButton> - </TimelineNavItem> - - {/* next */} - <TimelineNavItem $disable={canDisableRight}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onPrevious : onNext} - title={nextTitle} - aria-label={nextTitle} - aria-disabled={disableRight} - aria-controls="timeline-main-wrapper" - rotate={rotate ? 'TRUE' : 'FALSE'} - tabIndex={!disableRight ? 0 : -1} - > - <ChevronRightIcon /> - </TimelineNavButton> - </TimelineNavItem> - - {/* jump to last */} - <TimelineNavItem $disable={canDisableRight}> - <TimelineNavButton - mode={mode} - theme={theme} - onClick={flippedHorizontally ? onFirst : onLast} - title={jumpToLastTitle} - aria-label={jumpToLastTitle} - aria-disabled={disableRight} - aria-controls="timeline-main-wrapper" - tabIndex={!disableRight ? 0 : -1} - rotate={rotate ? 'TRUE' : 'FALSE'} - > - <ChevronsRightIcon /> - </TimelineNavButton> - </TimelineNavItem> - </> - )} - - {/* slideshow button */} - <TimelineNavItem> - {slideShowEnabled && ( - <TimelineNavButton - theme={theme} - onClick={handlePlayOrPause} - title={playOrPauseTile} - tabIndex={0} - aria-controls="timeline-main-wrapper" - aria-label={playOrPauseTile} - > - {slideShowRunning ? <StopIcon /> : <ReplayIcon />} - </TimelineNavButton> - )} - </TimelineNavItem> - - {/* dark toggle button */} - {enableDarkToggle ? ( - <TimelineNavItem $disable={slideShowRunning}> - <TimelineNavButton - theme={theme} - onClick={onToggleDarkMode} - title={isDark ? buttonTexts?.light : buttonTexts?.dark} - tabIndex={0} - aria-controls="timeline-main-wrapper" - aria-label={isDark ? buttonTexts?.light : buttonTexts?.dark} - > - {isDark ? <SunIcon /> : <MoonIcon />} - </TimelineNavButton> - </TimelineNavItem> - ) : null} - </TimelineNavWrapper> - </TimelineControlContainer> - ); -}; - -TimelineControl.displayName = 'Timeline Control'; - -export default TimelineControl; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-card-title.styles.ts | -
-
- |
- 100% | -24/24 | -76.92% | -10/13 | -100% | -5/5 | -100% | -24/24 | -
timeline-card-title.tsx | -
-
- |
- 100% | -49/49 | -100% | -4/4 | -100% | -1/1 | -100% | -49/49 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled from 'styled-components'; - -export const TitleWrapper = styled.div<{ - $fontSize?: string; - $hide?: boolean; - align?: string; - theme?: Theme; -}>` - border-radius: 0.2rem; - font-size: ${(p) => (p.$fontSize ? p.$fontSize : '1rem')}; - font-weight: 600; - overflow: hidden; - padding: 0.25rem; - visibility: ${(p) => (p.$hide ? 'hidden' : 'visible')}; - text-align: ${(p) => (p.align ? p.align : '')}; - color: ${(p) => (p.theme ? p.theme.titleColor : '')}; - - &.active { - background: ${(p) => p.theme?.secondary}; - color: ${(p) => - p.theme?.titleColorActive ? p.theme?.titleColorActive : p.theme?.primary}; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -1x -1x - | import { TitleModel } from '@models/TimelineCardTitleModel'; -import cls from 'classnames'; -import React, { useContext, useMemo } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { TitleWrapper } from './timeline-card-title.styles'; - -/** - * TimelineItemTitle component - * This component renders the title of a timeline item and applies appropriate styling based on the given props. - * - * @property {string} title - The text of the title. - * @property {boolean} active - Indicates whether the title is active or not. - * @property {Theme} theme - The theme object, used for styling. - * @property {string} align - The alignment of the title. - * @property {string} classString - Additional CSS classes for the title. - * @returns {JSX.Element} The TimelineItemTitle component. - */ -const TimelineItemTitle: React.FunctionComponent<TitleModel> = ({ - title, - active, - theme, - align, - classString, -}: TitleModel) => { - const TITLE_CLASS = 'timeline-item-title'; // Base class name for the title - - // Computed class name for the title, combining base class, active state, and additional classes - const titleClass = useMemo( - () => cls(TITLE_CLASS, active ? 'active' : '', classString), - [active, classString], - ); - - // Get font size from global context - const { fontSizes } = useContext(GlobalContext); - - return ( - <TitleWrapper - className={titleClass} - theme={theme} - $hide={!title} - align={align} - $fontSize={fontSizes?.title} - > - {title} - </TitleWrapper> - ); -}; - -export default TimelineItemTitle; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-horizontal.styles.ts | -
-
- |
- 100% | -35/35 | -66.66% | -2/3 | -100% | -0/0 | -100% | -35/35 | -
timeline-horizontal.tsx | -
-
- |
- 100% | -103/103 | -50% | -3/6 | -100% | -1/1 | -100% | -103/103 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import styled from 'styled-components';
-
-export const TimelineHorizontalWrapper = styled.ul<{ flipLayout?: boolean }>`
- display: flex;
- list-style: none;
- margin: 0;
- width: 100%;
- direction: ${(p) => (p.flipLayout ? 'rtl' : 'ltr')};
-
- &.vertical {
- flex-direction: column;
- }
- &.horizontal {
- flex-direction: row;
- }
-`;
-
-export const TimelineItemWrapper = styled.li<{ width: number }>`
- width: ${(p) => p.width}px;
- visibility: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- height: 150px;
- flex-direction: column;
-
- &.vertical {
- margin-bottom: 2rem;
- width: 100%;
- }
-
- &.visible {
- visibility: visible;
- }
-`;
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -1x -1x -1x -1x -1x -1x - | import { TimelineHorizontalModel } from '@models/TimelineHorizontalModel'; -import cls from 'classnames'; -import React, { ReactNode, useContext, useMemo } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import TimelineCard from '../timeline-elements/timeline-card/timeline-horizontal-card'; -import { - TimelineHorizontalWrapper, - TimelineItemWrapper, -} from './timeline-horizontal.styles'; - -/** - * TimelineHorizontal - * @property {TimelineHorizontalModel} items - The items to be displayed in the timeline. - * @property {(item: TimelineItem) => void} handleItemClick - Function to handle item click. - * @property {boolean} autoScroll - Whether to auto-scroll the timeline. - * @property {string} wrapperId - The ID of the wrapper element. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {() => void} onElapsed - Function to handle elapsed time. - * @property {React.ReactNode} contentDetailsChildren - The children nodes for content details. - * @property {boolean} hasFocus - Whether the timeline has focus. - * @property {React.ReactNode} iconChildren - The children nodes for icons. - * @property {number} nestedCardHeight - The height of the nested card. - * @property {boolean} isNested - Whether the card is nested. - * @returns {JSX.Element} The TimelineHorizontal component. - */ - -const TimelineHorizontal: React.FunctionComponent<TimelineHorizontalModel> = ({ - items, - handleItemClick, - autoScroll, - wrapperId, - slideShowRunning, - onElapsed, - contentDetailsChildren: children, - hasFocus, - iconChildren, - nestedCardHeight, - isNested, -}: TimelineHorizontalModel) => { - const { - mode = 'HORIZONTAL', - itemWidth = 200, - cardHeight, - flipLayout, - showAllCardsHorizontal, - theme, - cardWidth, - } = useContext(GlobalContext); - - console.log('showing', showAllCardsHorizontal); - - // Memoize the wrapper class to avoid unnecessary re-renders - const wrapperClass = useMemo( - () => - cls( - mode.toLowerCase(), - 'timeline-horizontal-container', - showAllCardsHorizontal ? 'show-all-cards-horizontal' : '', - ), - [mode, showAllCardsHorizontal], - ); - - const iconChildColln = React.Children.toArray(iconChildren); - - return ( - <TimelineHorizontalWrapper - className={wrapperClass} - flipLayout={flipLayout} - data-testid="timeline-collection" - > - {items.map((item, index) => ( - <TimelineItemWrapper - key={item.id} - width={itemWidth} - className={cls( - item.visible ? 'visible' : '', - 'timeline-horz-item-container', - )} - > - <TimelineCard - {...item} - onClick={handleItemClick} - autoScroll={autoScroll} - wrapperId={wrapperId} - theme={theme} - slideShowRunning={slideShowRunning} - cardHeight={cardHeight} - onElapsed={onElapsed} - customContent={children ? (children as ReactNode[])[index] : null} - hasFocus={hasFocus} - iconChild={iconChildColln[index]} - active={item.active} - cardWidth={cardWidth} - isNested={isNested} - nestedCardHeight={nestedCardHeight} - /> - </TimelineItemWrapper> - ))} - </TimelineHorizontalWrapper> - ); -}; - -export default TimelineHorizontal; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-point.tsx | -
-
- |
- 92.64% | -126/136 | -63.63% | -7/11 | -0% | -0/1 | -92.64% | -126/136 | -
timeline-vertical-item.tsx | -
-
- |
- 96.17% | -226/235 | -43.75% | -7/16 | -100% | -1/1 | -96.17% | -226/235 | -
timeline-vertical-shape.styles.ts | -
-
- |
- 100% | -53/53 | -62.5% | -5/8 | -100% | -3/3 | -100% | -53/53 | -
timeline-vertical.styles.ts | -
-
- |
- 85.41% | -123/144 | -57.14% | -12/21 | -100% | -5/5 | -85.41% | -123/144 | -
timeline-vertical.tsx | -
-
- |
- 93.04% | -107/115 | -80% | -8/10 | -100% | -1/1 | -93.04% | -107/115 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x - -20x -20x -20x -20x -20x -20x -20x -20x - - - - -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x - - - - - -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -1x -1x -1x -1x -1x -1x - | import { TimelinePointModel } from '@models/TimelineVerticalModel'; -import cls from 'classnames'; -import React, { memo, useContext, useEffect, useMemo, useRef } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import { Shape } from '../timeline-elements/timeline-card/timeline-horizontal-card.styles'; -import { - TimelinePointContainer, - TimelinePointWrapper, -} from './timeline-vertical-shape.styles'; - -/** - * TimelinePoint - * @property {string} className - The class name for the component. - * @property {string} id - The id of the timeline point. - * @property {() => void} onClick - Function to handle click event. - * @property {boolean} active - Whether the timeline point is active. - * @property {() => void} onActive - Function to handle active event. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {React.ReactNode} iconChild - The icon child nodes. - * @property {number} timelinePointDimension - The dimension of the timeline point. - * @property {number} lineWidth - The width of the line. - * @property {boolean} disableClickOnCircle - Whether the click on circle is disabled. - * @property {boolean} cardLess - Whether the card is less. - * @returns {JSX.Element} The TimelinePoint component. - */ -const TimelinePoint: React.FunctionComponent<TimelinePointModel> = memo( - (props: TimelinePointModel) => { - const { - className, - id, - onClick, - active, - onActive, - slideShowRunning, - iconChild, - timelinePointDimension, - lineWidth, - disableClickOnCircle, - cardLess, - } = props; - - const circleRef = useRef<HTMLButtonElement>(null); - const { - theme, - focusActiveItemOnLoad, - timelinePointShape, - disableTimelinePoint, - } = useContext(GlobalContext); - - const isFirstRender = useRef(true); - - // Determine if onActive can be invoked - const canInvokeOnActive = useMemo(() => { - if (focusActiveItemOnLoad) { - return active; - } else { - return active && !isFirstRender.current; - } - }, [active]); - - // Invoke onActive if conditions are met - useEffect(() => { - if (canInvokeOnActive) { - const circle = circleRef.current; - - circle && onActive(circle.offsetTop); - } - }, [canInvokeOnActive, active]); - - // Determine circle class - const circleClass = useMemo( - () => - cls({ - active, - 'using-icon': !!iconChild, - }), - [active, iconChild], - ); - - // Determine click handler props - const clickHandlerProps = useMemo( - () => - !disableClickOnCircle && { - onClick: (ev: React.MouseEvent) => { - ev.stopPropagation(); - if (id && onClick && !slideShowRunning) { - onClick(id); - } - }, - }, - [id, onClick, slideShowRunning, disableClickOnCircle], - ); - - // Update isFirstRender flag after first render - useEffect(() => { - if (isFirstRender.current) { - isFirstRender.current = false; - } - }, []); - - return ( - <TimelinePointWrapper - width={lineWidth} - bg={theme && theme.primary} - className={className} - data-testid="tree-leaf" - $cardLess={cardLess} - > - {/* {!disableTimelinePoint ? ( */} - <TimelinePointContainer - className={`${className} timeline-vertical-circle`} - {...clickHandlerProps} - ref={circleRef} - data-testid="tree-leaf-click" - aria-label="select timeline" - $hide={disableTimelinePoint} - > - <Shape - className={circleClass} - theme={theme} - dimension={timelinePointDimension} - $timelinePointShape={timelinePointShape} - > - {iconChild ? iconChild : null} - </Shape> - </TimelinePointContainer> - {/* ) : null} */} - </TimelinePointWrapper> - ); - }, - (prev, next) => prev.active === next.active, -); - -TimelinePoint.displayName = 'TimelinePoint'; - -export { TimelinePoint }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x - - - - - -29x -29x -29x -29x -29x - - - -29x -29x -29x -29x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -20x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x - -29x -29x -29x -29x -29x -1x -1x -1x -1x - | import { VerticalItemModel } from '@models/TimelineVerticalModel'; -import cls from 'classnames'; -import React, { useCallback, useContext, useMemo, useRef } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import TimelineCard from '../timeline-elements/timeline-card-content/timeline-card-content'; -import TimelineItemTitle from '../timeline-elements/timeline-item-title/timeline-card-title'; -import { TimelinePoint } from './timeline-point'; -import { - TimelineCardContentWrapper, - TimelineTitleWrapper, - VerticalItemWrapper, -} from './timeline-vertical.styles'; - -/** - * VerticalItem - * @property {boolean} active - Whether the vertical item is active. - * @property {boolean} alternateCards - Whether to alternate cards. - * @property {string} cardDetailedText - The detailed text of the card. - * @property {string} cardSubtitle - The subtitle of the card. - * @property {string} cardTitle - The title of the card. - * @property {string} url - The URL of the card. - * @property {string} className - The class name for the component. - * @property {React.ReactNode} contentDetailsChildren - The content details children nodes. - * @property {React.ReactNode} iconChild - The icon child nodes. - * @property {boolean} hasFocus - Whether the vertical item has focus. - * @property {string} id - The id of the vertical item. - * @property {React.ReactNode} media - The media nodes. - * @property {() => void} onActive - Function to handle active event. - * @property {() => void} onClick - Function to handle click event. - * @property {() => void} onElapsed - Function to handle elapsed event. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {string} title - The title of the vertical item. - * @property {boolean} visible - Whether the vertical item is visible. - * @property {React.ReactNode} timelineContent - The timeline content nodes. - * @property {Array} items - The items of the vertical item. - * @property {boolean} isNested - Whether the vertical item is nested. - * @property {number} nestedCardHeight - The height of the nested card. - * @returns {JSX.Element} The VerticalItem component. - */ -const VerticalItem: React.FunctionComponent<VerticalItemModel> = ( - props: VerticalItemModel, -) => { - const contentRef = useRef<HTMLLIElement>(null); - - const { - active, - alternateCards, - cardDetailedText, - cardSubtitle, - cardTitle, - url, - className, - contentDetailsChildren, - iconChild, - hasFocus, - id, - media, - onActive, - onClick, - onElapsed, - slideShowRunning, - title, - visible, - timelineContent, - items, - isNested, - nestedCardHeight, - } = props; - - const { - cardHeight, - mode, - flipLayout, - timelinePointDimension, - lineWidth, - disableClickOnCircle, - cardLess, - theme, - classNames, - textOverlay, - mediaHeight, - disableInteraction, - } = useContext(GlobalContext); - - // handler for onActive - const handleOnActive = useCallback( - (offset: number) => { - if (contentRef.current) { - const { offsetTop, clientHeight } = contentRef.current; - onActive(offsetTop + offset, offsetTop, clientHeight); - } - }, - [onActive], - ); - - // handler for read more - const handleShowMore = useCallback(() => { - setTimeout(() => { - handleOnActive(0); - }, 100); - }, [handleOnActive]); - - // timeline title - const Title = useMemo(() => { - return ( - <TimelineTitleWrapper - className={className} - $alternateCards={alternateCards} - mode={mode} - $hide={!title} - $flip={!alternateCards && flipLayout} - > - <TimelineItemTitle - title={title} - active={active && !disableInteraction} - theme={theme} - align={flipLayout ? 'left' : 'right'} - classString={classNames?.title} - /> - </TimelineTitleWrapper> - ); - }, [ - active, - title, - className, - alternateCards, - mode, - flipLayout, - theme, - classNames, - ]); - - const verticalItemClass = useMemo( - () => - cls({ [className]: true }, 'vertical-item-row', visible ? 'visible' : ''), - [className, visible], - ); - - const contentClass = cls('card-content-wrapper', visible ? 'visible' : '', { - [className]: true, - }); - - // timeline circle - const TimelinePointMemo = useMemo( - () => ( - <TimelinePoint - active={active} - alternateCards={alternateCards} - className={className} - id={id} - mode={mode} - onActive={handleOnActive} - onClick={onClick} - slideShowRunning={slideShowRunning} - iconChild={iconChild} - timelinePointDimension={timelinePointDimension} - lineWidth={lineWidth} - disableClickOnCircle={disableClickOnCircle} - cardLess={cardLess} - /> - ), - [ - slideShowRunning, - active, - alternateCards, - className, - id, - mode, - handleOnActive, - onClick, - iconChild, - timelinePointDimension, - lineWidth, - disableClickOnCircle, - cardLess, - ], - ); - - return ( - <VerticalItemWrapper - $alternateCards={alternateCards} - $cardHeight={isNested ? nestedCardHeight : cardHeight} - className={verticalItemClass} - data-testid="vertical-item-row" - key={id} - ref={contentRef} - $cardLess={cardLess} - $isNested={isNested} - theme={theme} - > - {/* title */} - {!isNested ? Title : null} - - {/* card section */} - <TimelineCardContentWrapper - className={contentClass} - $alternateCards={alternateCards} - $noTitle={!title} - $flip={!alternateCards && flipLayout} - height={textOverlay ? mediaHeight : cardHeight} - > - {!cardLess ? ( - // <span></span> - <TimelineCard - active={active} - branchDir={className} - content={cardSubtitle} - customContent={contentDetailsChildren} - detailedText={cardDetailedText} - hasFocus={hasFocus} - id={id} - media={media} - onClick={onClick} - onElapsed={onElapsed} - onShowMore={handleShowMore} - slideShowActive={slideShowRunning} - theme={theme} - title={cardTitle} - url={url} - flip={!alternateCards && flipLayout} - timelineContent={timelineContent} - items={items} - isNested={isNested} - nestedCardHeight={nestedCardHeight} - /> - ) : null} - </TimelineCardContentWrapper> - {!isNested ? TimelinePointMemo : null} - </VerticalItemWrapper> - ); -}; - -VerticalItem.displayName = 'VerticalItem'; - -export default VerticalItem; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import styled from 'styled-components'; - -export const TimelinePointWrapper = styled.div<{ - $cardLess?: boolean; - bg?: string; - width?: number; -}>` - align-items: center; - display: flex; - justify-content: center; - position: relative; - width: 5%; - - &.left { - order: 2; - } - - &.right { - order: 1; - } - - &::before { - background: ${(p) => p.bg}; - width: ${(p) => (p.width ? `${p.width}px` : '4px')}; - height: 2rem; - position: absolute; - content: ''; - display: block; - left: 50%; - top: -1rem; - transform: translateY(-50%) translateX(-50%); - } - - &::after { - background: ${(p) => p.bg}; - content: ''; - display: block; - height: 100%; - left: 50%; - position: absolute; - width: ${(p) => (p.width ? `${p.width}px` : '4px')}; - z-index: 0; - transform: translateX(-50%); - } -`; - -export const TimelinePointContainer = styled.button<{ $hide?: boolean }>` - position: relative; - z-index: 1; - visibility: ${(p) => (p.$hide ? 'hidden' : 'visible')}; - background: none; - border: 0; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x - - - - - - - - - -29x -29x -29x -29x -29x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -29x -21x -29x - -8x -8x -8x -29x -1x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x -29x - - - - - - - - - - -29x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -20x - -20x -20x -20x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled, { css, keyframes } from 'styled-components'; - -export const TimelineVerticalWrapper = styled.div` - display: flex; - flex-direction: column; - width: 100%; - padding: 1em; - outline: 0; -`; - -const animateVisible = keyframes` - from { - opacity: 0; - visibility: hidden; - } - to { - opacity: 1; - visibility: visible; - } -`; - -export const VerticalItemWrapper = styled.li<{ - $alternateCards?: boolean; - $cardHeight?: number; - $cardLess?: boolean; - $isNested?: boolean; - theme?: Theme; -}>` - display: flex; - position: relative; - visibility: hidden; - width: 100%; - align-items: stretch; - justify-content: center; - margin: 1rem 0; - list-style: none; - - &.left { - margin-right: auto; - } - &.right { - margin-left: auto; - } - - &.visible { - visibility: visible; - } - - ${(p) => - p.$isNested - ? css` - position: relative; - - &:not(:last-child)::after { - content: ''; - position: absolute; - width: 2px; - height: 2rem; - background: ${(p) => p.theme.primary}; - left: 50%; - transform: translateX(-50%); - bottom: -2rem; - } - ` - : css``} -`; - -export const TimelineCardContentWrapper = styled.div<{ - $alternateCards?: boolean; - $cardLess?: boolean; - $flip?: boolean; - $noTitle?: boolean; - height?: number; -}>` - visibility: hidden; - position: relative; - display: flex; - align-items: center; - ${(p) => { - if (p.$alternateCards) { - return `width: 50%;`; - } else if (p.$noTitle) { - return `width: 95%;`; - } else { - return `width: 85%;`; - } - }} - ${(p) => { - if (!p.$flip) { - return ` - &.left { - order: 1; - justify-content: flex-end; - } - &.right { - order: 3; - justify-content: flex-start; - } - `; - } else { - return ` - justify-content: flex-end; - &.left { - order: 3; - } - &.right { - order: 1; - } - `; - } - }} - &.visible { - visibility: visible; - animation: ${animateVisible} 0.25s ease-in; - } -`; - -export const TimelineTitleWrapper = styled.div<{ - $alternateCards?: boolean; - $flip?: boolean; - $hide?: boolean; - mode?: TimelineMode; -}>` - align-items: center; - display: ${(p) => (p.$hide && p.mode === 'VERTICAL' ? 'none' : 'flex')}; - ${(p) => (p.$alternateCards ? 'width: 50%' : 'width: 10%')}; - - &.left { - justify-content: ${(p) => (p.$flip ? 'flex-end' : 'flex-start')}; - order: ${(p) => (p.$flip && p.mode === 'VERTICAL_ALTERNATING' ? '1' : '3')}; - } - - &.right { - ${(p) => - p.$flip - ? ` - order: 3; - justify-content: flex-start;` - : `order: 1; - justify-content: flex-end;`}; - } -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x - - - - - - -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -9x -27x -27x -27x -27x -21x -27x -6x -6x -27x -27x -27x - -27x -27x -27x - -27x -9x -18x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -27x -9x -9x -9x -9x -1x -1x -1x -1x - | import { TimelineVerticalModel } from '@models/TimelineVerticalModel'; -import React, { useCallback } from 'react'; -import TimelineVerticalItem from './timeline-vertical-item'; -import { TimelineVerticalWrapper } from './timeline-vertical.styles'; - -/** - * TimelineVertical - * @property {boolean} alternateCards - Whether to alternate cards. - * @property {() => void} autoScroll - Function to handle auto scroll. - * @property {React.ReactNode} contentDetailsChildren - The content details children nodes. - * @property {boolean} enableOutline - Whether to enable outline. - * @property {boolean} hasFocus - Whether the timeline has focus. - * @property {React.ReactNode} iconChildren - The icon children nodes. - * @property {Array} items - The items of the timeline. - * @property {string} mode - The mode of the timeline. - * @property {() => void} onClick - Function to handle click event. - * @property {() => void} onElapsed - Function to handle elapsed event. - * @property {() => void} onOutlineSelection - Function to handle outline selection. - * @property {boolean} slideShowRunning - Whether the slideshow is running. - * @property {Object} theme - The theme of the timeline. - * @property {boolean} cardLess - Whether the card is less. - * @property {number} nestedCardHeight - The height of the nested card. - * @returns {JSX.Element} The TimelineVertical component. - */ -const TimelineVertical: React.FunctionComponent<TimelineVerticalModel> = ({ - alternateCards = true, - autoScroll, - contentDetailsChildren, - enableOutline, - hasFocus, - iconChildren, - items, - mode, - onClick, - onElapsed, - onOutlineSelection, - slideShowRunning, - theme, - cardLess, - nestedCardHeight, -}: TimelineVerticalModel) => { - // check if the timeline that has become active is visible. - // if not auto scroll the content and bring it to the view. - const handleOnActive = useCallback( - (offset: number, wrapperOffset: number, height: number) => { - autoScroll({ - contentHeight: height, - contentOffset: wrapperOffset, - pointOffset: offset, - }); - }, - [autoScroll], - ); - - // todo remove this - const handleOnShowMore = useCallback(() => {}, []); - - return ( - <TimelineVerticalWrapper data-testid="tree-main" role="list"> - {/* {enableOutline && ( - <TimelineOutline - theme={theme} - mode={mode} - items={outlineItems} - onSelect={onOutlineSelection} - /> - )} */} - {items.map((item, index) => { - let className = ''; - - // in tree mode alternate cards position - if (alternateCards) { - className = index % 2 === 0 ? 'left' : 'right'; - } else { - className = 'right'; - } - - const contentDetails = - (contentDetailsChildren && - (contentDetailsChildren as React.ReactNode[])[index]) || - null; - - const customIcon = Array.isArray(iconChildren) - ? iconChildren[index] - : index === 0 - ? iconChildren - : null; - - return ( - <TimelineVerticalItem - {...item} - alternateCards={alternateCards} - className={className} - contentDetailsChildren={contentDetails} - iconChild={customIcon} - hasFocus={hasFocus} - index={index} - key={item.id} - onActive={handleOnActive} - onClick={onClick} - onElapsed={onElapsed} - onShowMore={handleOnShowMore} - slideShowRunning={slideShowRunning} - cardLess={cardLess} - nestedCardHeight={nestedCardHeight} - /> - ); - })} - </TimelineVerticalWrapper> - ); -}; - -TimelineVertical.displayName = 'TimelineVertical'; - -export default TimelineVertical; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
timeline-popover-elements.tsx | -
-
- |
- 97.03% | -131/135 | -100% | -10/10 | -42.85% | -3/7 | -97.03% | -131/135 | -
timeline-toolbar.tsx | -
-
- |
- 86.84% | -99/114 | -60% | -6/10 | -25% | -1/4 | -86.84% | -99/114 | -
timeline.style.ts | -
-
- |
- 95.06% | -154/162 | -57.14% | -12/21 | -100% | -5/5 | -95.06% | -154/162 | -
timeline.tsx | -
-
- |
- 72.85% | -357/490 | -75.6% | -31/41 | -18.18% | -2/11 | -72.85% | -357/490 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -15x -15x -15x -15x -15x -15x -10x -10x -10x - - -10x -10x -10x -10x -10x -10x - - -10x -10x -10x -10x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -15x -2x -11x -15x -15x -15x -15x -15x -15x -15x -1x -1x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -4x -4x -4x -4x -4x -4x -2x -2x -2x -2x -2x -2x -2x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import { FunctionComponent, useContext, useMemo } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import { List } from '../elements/list/list'; -import { ListItemModel } from '../elements/list/list.model'; -import { PopOver } from '../elements/popover'; - -type LayoutSwitcherProp = { - initialTimelineMode?: TimelineMode | 'HORIZONTAL_ALL'; - isDarkMode: boolean; - mode?: TimelineMode; - onUpdateTimelineMode: (s: string) => void; - position: 'top' | 'bottom'; - theme: Theme; -}; - -type QuickJumpProp = { - activeItem: number; - isDarkMode: boolean; - items: ListItemModel[]; - onActivateItem: (id: string) => void; - position: 'top' | 'bottom'; - theme: Theme; -}; - -const LayoutSwitcher: FunctionComponent<LayoutSwitcherProp> = ({ - onUpdateTimelineMode, - theme, - mode, - isDarkMode, - position, -}: LayoutSwitcherProp) => { - const { showAllCardsHorizontal, buttonTexts } = useContext(GlobalContext); - - const activeTimelineMode = useMemo( - () => mode, - [showAllCardsHorizontal, mode], - ); - - const verticalItems = useMemo( - () => [ - { - id: 'VERTICAL', - onSelect: () => onUpdateTimelineMode('VERTICAL'), - selected: activeTimelineMode === 'VERTICAL', - title: 'Default', - }, - { - id: 'VERTICAL_ALTERNATING', - onSelect: () => onUpdateTimelineMode('VERTICAL_ALTERNATING'), - selected: activeTimelineMode === 'VERTICAL_ALTERNATING', - title: 'Alternating', - }, - ], - [activeTimelineMode], - ); - - // horizontal list OF options when the mode is `HORIZONTAL` - const horizontalItems = useMemo( - () => [ - { - id: 'HORIZONTAL', - onSelect: () => { - onUpdateTimelineMode('HORIZONTAL'); - }, - selected: activeTimelineMode === 'HORIZONTAL', - title: 'Default', - }, - { - id: 'HORIZONTAL_ALL', - onSelect: () => { - onUpdateTimelineMode('HORIZONTAL_ALL'); - }, - selected: activeTimelineMode === 'HORIZONTAL_ALL', - title: 'Show all cards', - }, - ], - [activeTimelineMode], - ); - - return ( - <PopOver - placeholder={buttonTexts.changeLayout} - position={position} - theme={theme} - isDarkMode={isDarkMode} - > - <List - items={ - mode === 'HORIZONTAL' || mode === 'HORIZONTAL_ALL' - ? horizontalItems - : verticalItems - } - theme={theme} - multiSelectable - /> - </PopOver> - ); -}; - -const QuickJump: FunctionComponent<QuickJumpProp> = ({ - activeItem, - items, - theme, - onActivateItem, - isDarkMode, - position, -}: QuickJumpProp) => { - const { buttonTexts } = useContext(GlobalContext); - return ( - <PopOver - placeholder={buttonTexts.jumpTo} - position={position} - theme={theme} - width={'400px'} - isDarkMode={isDarkMode} - > - <List - items={items.map((item, index) => ({ - active: index === activeItem, - description: item.description, - id: item.id, - label: item.title, - onSelect: () => {}, - title: item.title, - }))} - theme={theme} - onClick={onActivateItem} - /> - </PopOver> - ); -}; - -export { LayoutSwitcher, QuickJump }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -11x -11x -11x -11x -7x - -7x -11x -11x -11x -11x -7x - -7x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x - - - - - - - - - - - - -10x -11x -10x -10x -10x -10x -10x -10x -10x - -11x -11x -11x -1x -1x -1x - | // Import necessary dependencies -import { FunctionComponent, useContext, useMemo } from 'react'; -import { GlobalContext } from '../GlobalContext'; -import TimelineControl from '../timeline-elements/timeline-control/timeline-control'; -import { Toolbar } from '../toolbar'; -import { LayoutSwitcher, QuickJump } from './timeline-popover-elements'; -import { TimelineToolbarProps } from './timeline-toolbar.model'; - -// Define the TimelineToolbar component -const TimelineToolbar: FunctionComponent<TimelineToolbarProps> = ({ - activeTimelineItem, - slideShowEnabled, - slideShowRunning, - flipLayout, - toggleDarkMode, - onPaused, - onFirst, - onLast, - onNext, - onPrevious, - onRestartSlideshow, - totalItems, - items = [], - id, - onActivateTimelineItem, - onUpdateTimelineMode, - mode, -}) => { - // Access the global context - const { theme, cardLess, enableQuickJump, darkMode, toolbarPosition } = - useContext(GlobalContext); - - // Define the toolbar items - const toolbarItems = useMemo(() => { - return [ - { - label: 'Timeline Controls', - name: 'timeline_control', - onSelect: () => {}, - }, - { - label: 'timeline_popover', - name: 'popover', - onSelect: () => {}, - }, - { - label: 'layout_popover', - name: 'popover', - onSelect: () => {}, - }, - ]; - }, []); - - // Determine if the left arrow should be disabled - const disableLeft = useMemo(() => { - return flipLayout - ? activeTimelineItem === totalItems - 1 - : activeTimelineItem === 0; - }, [flipLayout, activeTimelineItem, totalItems]); - - // Determine if the right arrow should be disabled - const disableRight = useMemo(() => { - return flipLayout - ? activeTimelineItem === 0 - : activeTimelineItem === totalItems - 1; - }, [flipLayout, activeTimelineItem, totalItems]); - - // Render the TimelineToolbar component - return ( - <Toolbar items={toolbarItems} theme={theme}> - <TimelineControl - disableLeft={disableLeft} - disableRight={disableRight} - id={id} - onFirst={onFirst} - onLast={onLast} - onNext={onNext} - onPrevious={onPrevious} - onReplay={onRestartSlideshow} - slideShowEnabled={slideShowEnabled} - slideShowRunning={slideShowRunning} - isDark={darkMode} - onToggleDarkMode={toggleDarkMode} - onPaused={onPaused} - /> - {enableQuickJump ? ( - <QuickJump - activeItem={activeTimelineItem} - isDarkMode={darkMode} - items={items.map((item) => ({ - ...item, - description: item.cardSubtitle, - title: item.title, - }))} - onActivateItem={onActivateTimelineItem} - theme={theme} - position={toolbarPosition} - /> - ) : null} - {!cardLess ? ( - <LayoutSwitcher - isDarkMode={darkMode} - theme={theme} - onUpdateTimelineMode={onUpdateTimelineMode} - mode={mode} - position={toolbarPosition} - /> - ) : null} - </Toolbar> - ); -}; - -// Export the TimelineToolbar component -export { TimelineToolbar }; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -10x - -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -10x - -10x -10x -10x -10x -10x -10x -10x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import styled from 'styled-components'; -import { ScrollBar } from '../common/styles'; - -export const Wrapper = styled.div<{ - $hideControls?: boolean; - cardPositionHorizontal?: 'TOP' | 'BOTTOM'; -}>` - display: flex; - flex-direction: column; - /* cannot remove this */ - height: 100%; - - &:focus { - outline: 0; - } - - overflow: hidden; - position: relative; - width: 100%; - - ${(p) => - p.cardPositionHorizontal === 'TOP' && !p.$hideControls - ? ` - & > div:nth-of-type(1) { - order: 2; - } - & > div:nth-of-type(2) { - order: 3; - } - & > div:nth-of-type(3) { - order: 1; - } - ` - : ''}; - - ${(p) => - p.cardPositionHorizontal === 'TOP' && p.$hideControls - ? ` - & > div:nth-of-type(1) { - order: 2; - } - & > div:nth-of-type(2) { - order: 1; - } - ` - : ''}; - - &.horizontal { - justify-content: flex-start; - } - - &.js-focus-visible :focus:not(.focus-visible) { - // outline: 0; - } - - &.js-focus-visible .focus-visible { - outline: 2px solid #528deb; - } -`; - -export const TimelineMainWrapper = styled.div<{ - $scrollable?: boolean | { scrollbar: boolean }; - mode?: TimelineMode; - position?: 'top' | 'bottom'; - theme?: Theme; -}>` - align-items: flex-start; - display: flex; - justify-content: center; - overflow-y: auto; - overflow-x: hidden; - overscroll-behavior: contain; - ${(p) => (p.mode === 'HORIZONTAL' ? 'position: relative' : '')}; - scroll-behavior: smooth; - width: 100%; - order: ${(p) => (p.position === 'top' ? 1 : 0)}; - - ${ScrollBar} - - &.horizontal { - min-height: 150px; - } - - padding: ${({ $scrollable }) => (!$scrollable ? '0 1rem 0' : '')}; -`; - -export const TimelineMain = styled.div` - position: absolute; - top: 50%; - left: 0; - display: flex; - align-items: center; - transition: all 0.2s ease; - transform: translate(0, -50%); - - &.vertical { - align-items: flex-start; - justify-content: flex-start; - width: 100%; - height: 100%; - } -`; - -export const Outline = styled.div<{ color?: string; height?: number }>` - position: absolute; - right: 0; - left: 0; - width: 100%; - height: ${(p) => `${p.height}px`}; - margin-right: auto; - margin-left: auto; - background: ${(p) => p.color}; -`; - -export const TimelineControlContainer = styled.div<{ - active?: boolean; - mode?: TimelineMode; -}>` - display: flex; - align-items: center; - justify-content: center; - min-height: 3rem; - - filter: ${(p) => { - if (p.active) { - return `opacity(1);`; - } else { - return `opacity(0.9);`; - } - }}; - - &.hide { - visibility: hidden; - } - - &.show { - visibility: visible; - } -`; - -export const TimelineContentRender = styled.div<{ $showAllCards?: boolean }>` - display: flex; - align-items: flex-start; - justify-content: ${(p) => (p.$showAllCards ? 'flex-start' : 'center')}; - width: 98%; - margin-right: auto; - margin-left: auto; - overflow-x: hidden; -`; - -export const ToolbarWrapper = styled.div<{ position: 'top' | 'bottom' }>` - display: flex; - font-weight: bold; - text-align: center; - text-decoration: none; - border-radius: 6px; - width: 100%; - margin: ${(p) => (p.position === 'top' ? '0 0 20px 0' : '20px 0 0 0')}; - order: ${(p) => (p.position === 'top' ? 0 : 1)}; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360 -361 -362 -363 -364 -365 -366 -367 -368 -369 -370 -371 -372 -373 -374 -375 -376 -377 -378 -379 -380 -381 -382 -383 -384 -385 -386 -387 -388 -389 -390 -391 -392 -393 -394 -395 -396 -397 -398 -399 -400 -401 -402 -403 -404 -405 -406 -407 -408 -409 -410 -411 -412 -413 -414 -415 -416 -417 -418 -419 -420 -421 -422 -423 -424 -425 -426 -427 -428 -429 -430 -431 -432 -433 -434 -435 -436 -437 -438 -439 -440 -441 -442 -443 -444 -445 -446 -447 -448 -449 -450 -451 -452 -453 -454 -455 -456 -457 -458 -459 -460 -461 -462 -463 -464 -465 -466 -467 -468 -469 -470 -471 -472 -473 -474 -475 -476 -477 -478 -479 -480 -481 -482 -483 -484 -485 -486 -487 -488 -489 -490 -491 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -7x -7x - - -7x -7x - - -7x -11x -11x -11x -11x -11x -11x -11x -11x -11x - - - - -11x -11x -11x -11x -11x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x - - - - -11x -11x -11x -1x -1x -1x -1x -11x -11x -11x -1x -1x -1x -1x -11x -11x -11x -11x -11x - - - - - - - - - - - - - - - - - - - - - - -11x -11x -11x -11x - - - - - - - - - - - - - - -11x -11x -7x -7x -7x - - -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - -1x -7x -11x -11x -11x - - - - - -11x -11x -7x -7x - - -7x -1x -7x -6x -6x -11x -11x -11x -7x -7x -7x -7x -7x -5x -5x -15x -15x -15x -5x -5x -7x -7x -7x - - - - - - - - -7x -7x -6x -6x - - - - - - - - - - - - - - - - - - -6x -6x -6x -6x -6x -6x -7x -7x -7x -6x -6x -7x -7x -11x -11x -11x -11x - - - - - -11x -11x -11x -11x - - - - - - - - - - - -11x -11x -11x -7x -7x -7x -7x -11x -11x -11x -11x -11x -11x -11x -11x -3x -3x -11x -11x - - - - -11x -11x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x - -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x - - - - - - - - - - - - - - - - - -11x -11x -11x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x - -7x -7x -7x -7x -7x -7x -7x -3x -11x -11x -11x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - -1x -1x -1x -1x -1x -1x -9x -11x -11x -11x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x - -2x -2x -2x -2x -2x -2x -2x -8x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -11x -1x -1x -1x -1x - | import { Scroll } from '@models/TimelineHorizontalModel'; -import { TimelineCardModel } from '@models/TimelineItemModel'; -import { TimelineModel } from '@models/TimelineModel'; -import { getUniqueID } from '@utils/index'; -import cls from 'classnames'; -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { GlobalContext } from '../GlobalContext'; -import { useMatchMedia } from '../effects/useMatchMedia'; -import useNewScrollPosition from '../effects/useNewScrollPosition'; -import TimelineHorizontal from '../timeline-horizontal/timeline-horizontal'; -import TimelineVertical from '../timeline-vertical/timeline-vertical'; -import { TimelineToolbar } from './timeline-toolbar'; -import { - Outline, - TimelineContentRender, - TimelineMain, - TimelineMainWrapper, - ToolbarWrapper, - Wrapper, -} from './timeline.style'; - -const Timeline: React.FunctionComponent<TimelineModel> = ( - props: TimelineModel, -) => { - // de-structure the props - const { - activeTimelineItem, - contentDetailsChildren, - iconChildren, - items = [], - onFirst, - onLast, - onNext, - onPrevious, - onRestartSlideshow, - onTimelineUpdated, - onItemSelected, - onOutlineSelection, - slideShowEnabled, - slideShowRunning, - mode = 'HORIZONTAL', - enableOutline = false, - hideControls = false, - nestedCardHeight, - isChild = false, - onPaused, - uniqueId, - noUniqueId, - } = props; - - const { - cardPositionHorizontal, - disableNavOnKey, - flipLayout, - itemWidth = 200, - lineWidth, - onScrollEnd, - scrollable = true, - showAllCardsHorizontal, - theme, - darkMode, - toggleDarkMode, - verticalBreakPoint = 768, - enableBreakPoint, - updateHorizontalAllCards, - toolbarPosition, - } = useContext(GlobalContext); - - const [newOffSet, setNewOffset] = useNewScrollPosition(mode, itemWidth); - const observer = useRef<IntersectionObserver | null>(null); - const [hasFocus, setHasFocus] = useState(false); - const horizontalContentRef = useRef<HTMLDivElement | null>(null); - const [timelineMode, setTimelineMode] = useState( - mode === 'HORIZONTAL' && showAllCardsHorizontal ? 'HORIZONTAL_ALL' : mode, - ); - - const activeItemIndex = useRef<number>(activeTimelineItem); - - // reference to the timeline - const timelineMainRef = useRef<HTMLDivElement>(null); - - const canScrollTimeline = useMemo(() => { - if (!slideShowRunning) { - if (typeof scrollable === 'boolean') { - return scrollable; - } - - if (typeof scrollable === 'object' && scrollable.scrollbar) { - return scrollable.scrollbar; - } - } - }, [slideShowRunning, scrollable]); - - const id = useRef( - `react-chrono-timeline-${noUniqueId ? uniqueId : getUniqueID()}`, - ); - - useMatchMedia( - `(min-width: 100px) and (max-width: ${verticalBreakPoint}px)`, - () => { - if (mode === 'VERTICAL_ALTERNATING') { - setTimelineMode('VERTICAL'); - } - }, - enableBreakPoint, - ); - - // handlers for navigation - const handleNext = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = Math.min( - activeItemIndex.current + 1, - items.length - 1, - ); - onNext?.(); - } - }, [hasFocus, onNext]); - - const handlePrevious = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = Math.max(activeItemIndex.current - 1, 0); - onPrevious?.(); - } - }, [hasFocus, onPrevious]); - - const handleFirst = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = 0; - onFirst?.(); - } - }, [hasFocus, onFirst]); - - const handleLast = useCallback(() => { - if (hasFocus) { - activeItemIndex.current = items.length - 1; - onLast?.(); - } - }, [hasFocus, onLast]); - - // handler for keyboard navigation - const handleKeySelection = useCallback( - (event: React.KeyboardEvent<HTMLDivElement>) => { - const { key } = event; - - if (mode === 'HORIZONTAL' && key === 'ArrowRight') { - flipLayout ? handlePrevious() : handleNext(); - } else if (mode === 'HORIZONTAL' && key === 'ArrowLeft') { - flipLayout ? handleNext() : handlePrevious(); - } else if ( - (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && - key === 'ArrowDown' - ) { - handleNext(); - } else if ( - (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && - key === 'ArrowUp' - ) { - handlePrevious(); - } else if (key === 'Home') { - handleFirst(); - } else if (key === 'End') { - handleLast(); - } - }, - [handleNext, handlePrevious, handleLast], - ); - - const handleTimelineItemClick = (itemId?: string, isSlideShow?: boolean) => { - if (itemId) { - for (let idx = 0; idx < items.length; idx++) { - if (items[idx].id === itemId) { - activeItemIndex.current = idx; - if (isSlideShow && idx < items.length - 1) { - onTimelineUpdated?.(idx + 1); - } else { - onTimelineUpdated?.(idx); - } - break; - } - } - } - }; - - useEffect(() => { - const activeItem = items[activeTimelineItem || 0]; - - if (slideShowRunning) { - activeItemIndex.current = activeTimelineItem; - } - - if (items.length && activeItem) { - // const item = items[activeItem]; - const { title, cardTitle, cardSubtitle, cardDetailedText } = activeItem; - onItemSelected?.({ - cardDetailedText, - cardSubtitle, - cardTitle, - index: activeItemIndex.current, - title, - }); - - if (mode === 'HORIZONTAL') { - const card = horizontalContentRef.current?.querySelector( - `#timeline-card-${activeItem.id}`, - ); - - const cardRect = card?.getBoundingClientRect(); - const contentRect = - horizontalContentRef.current?.getBoundingClientRect(); - - if (cardRect && contentRect) { - const { width: cardWidth, left: cardLeft } = cardRect; - const { width: contentWidth, left: contentLeft } = contentRect; - setTimeout(() => { - const ele = horizontalContentRef.current as HTMLElement; - ele.style.scrollBehavior = 'smooth'; - ele.scrollLeft += - cardLeft - contentLeft + cardWidth / 2 - contentWidth / 2; - }, 100); - } - } - } - }, [activeTimelineItem, items.length, slideShowRunning]); - - const handleScroll = (scroll: Partial<Scroll>) => { - const element = timelineMainRef.current; - if (element) { - setNewOffset(element, scroll); - } - }; - - useEffect(() => { - const ele = timelineMainRef.current; - if (!ele) { - return; - } - if (mode === 'HORIZONTAL') { - ele.scrollLeft = Math.max(newOffSet, 0); - } else { - ele.scrollTop = newOffSet; - } - }, [newOffSet]); - - useEffect(() => { - // setup observer for the timeline elements - setTimeout(() => { - const element = timelineMainRef.current; - - if (element) { - const childElements = element.querySelectorAll('.vertical-item-row'); - Array.from(childElements).forEach((elem) => { - if (observer.current) { - observer.current.observe(elem); - } - }); - } - }, 0); - - const toggleMedia = (elem: HTMLElement, state: string) => { - elem - .querySelectorAll('img,video') - .forEach( - (ele) => - ((ele as HTMLElement).style.visibility = - state === 'hide' ? 'hidden' : 'visible'), - ); - }; - - if (mode !== 'HORIZONTAL') { - observer.current = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - const element = entry.target as HTMLDivElement; - if (entry.isIntersecting) { - // show img and video when visible. - toggleMedia(element, 'show'); - } else { - // hide img and video when not visible. - toggleMedia(element, 'hide'); - // pause YouTube embeds - element.querySelectorAll('iframe').forEach((element) => { - element.contentWindow?.postMessage( - '{"event":"command","func":"stopVideo","args":""}', - '*', - ); - }); - } - }); - }, - { - root: timelineMainRef.current, - threshold: 0, - }, - ); - } - - return () => { - if (observer.current) { - observer.current.disconnect(); - } - }; - // eslint-disable-next-line - }, []); - - const handleKeyDown = useCallback( - (evt: React.KeyboardEvent<HTMLDivElement>) => { - if (!disableNavOnKey && !slideShowRunning) { - setHasFocus(true); - handleKeySelection(evt); - } - }, - [disableNavOnKey, slideShowRunning, handleKeySelection], - ); - - const handleTimelineUpdate = useCallback((mode: string) => { - if (mode === 'VERTICAL') { - setTimelineMode('VERTICAL'); - } else if (mode === 'HORIZONTAL') { - setTimelineMode('HORIZONTAL'); - updateHorizontalAllCards?.(false); - } else if (mode === 'VERTICAL_ALTERNATING') { - setTimelineMode('VERTICAL_ALTERNATING'); - } else if (mode === 'HORIZONTAL_ALL') { - setTimelineMode('HORIZONTAL_ALL'); - updateHorizontalAllCards?.(true); - } - }, []); - - const wrapperClass = useMemo(() => { - return cls(mode.toLocaleLowerCase(), { - 'focus-visible': !isChild, - 'js-focus-visible': !isChild, - }); - }, [mode, isChild]); - - return ( - <Wrapper - onKeyDown={handleKeyDown} - className={wrapperClass} - cardPositionHorizontal={cardPositionHorizontal} - onMouseDown={() => { - setHasFocus(true); - }} - $hideControls={hideControls} - onKeyUp={(evt) => { - if (evt.key === 'Escape') { - onPaused?.(); - } - }} - > - {!isChild ? ( - <ToolbarWrapper position={toolbarPosition}> - <TimelineToolbar - activeTimelineItem={activeTimelineItem} - totalItems={items.length} - slideShowEnabled={slideShowEnabled} - slideShowRunning={slideShowRunning} - onFirst={handleFirst} - onLast={handleLast} - onNext={handleNext} - onPrevious={handlePrevious} - onRestartSlideshow={onRestartSlideshow} - darkMode={darkMode} - toggleDarkMode={toggleDarkMode} - onPaused={onPaused} - id={id.current} - flipLayout={flipLayout} - items={items} - onActivateTimelineItem={handleTimelineItemClick} - onUpdateTimelineMode={handleTimelineUpdate} - mode={timelineMode} - /> - </ToolbarWrapper> - ) : null} - <TimelineMainWrapper - ref={timelineMainRef} - $scrollable={canScrollTimeline} - className={`${mode.toLowerCase()} timeline-main-wrapper`} - id="timeline-main-wrapper" - data-testid="timeline-main-wrapper" - theme={theme} - mode={mode} - position={toolbarPosition} - onScroll={(ev) => { - const target = ev.target as HTMLElement; - let scrolled = 0; - - if (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') { - scrolled = target.scrollTop + target.clientHeight; - - if (target.scrollHeight - scrolled < 1) { - onScrollEnd?.(); - } - } else { - scrolled = target.scrollLeft + target.offsetWidth; - - if (target.scrollWidth === scrolled) { - onScrollEnd?.(); - } - } - }} - > - {/* VERTICAL ALTERNATING */} - {timelineMode === 'VERTICAL_ALTERNATING' ? ( - <TimelineVertical - activeTimelineItem={activeTimelineItem} - autoScroll={handleScroll} - contentDetailsChildren={contentDetailsChildren} - hasFocus={hasFocus} - iconChildren={iconChildren} - items={items as TimelineCardModel[]} - mode={timelineMode} - onClick={handleTimelineItemClick} - onElapsed={(itemId?: string) => - handleTimelineItemClick(itemId, true) - } - onOutlineSelection={onOutlineSelection} - slideShowRunning={slideShowRunning} - theme={theme} - enableOutline={enableOutline} - nestedCardHeight={nestedCardHeight} - /> - ) : null} - - {/* HORIZONTAL */} - {timelineMode === 'HORIZONTAL' || timelineMode === 'HORIZONTAL_ALL' ? ( - <TimelineMain className={mode.toLowerCase()}> - <Outline color={theme && theme.primary} height={lineWidth} /> - <TimelineHorizontal - autoScroll={handleScroll} - contentDetailsChildren={contentDetailsChildren} - handleItemClick={handleTimelineItemClick} - hasFocus={hasFocus} - iconChildren={iconChildren} - items={items as TimelineCardModel[]} - mode={timelineMode} - onElapsed={(itemId?: string) => - handleTimelineItemClick(itemId, true) - } - slideShowRunning={slideShowRunning} - wrapperId={id.current} - nestedCardHeight={nestedCardHeight} - /> - </TimelineMain> - ) : null} - - {/* VERTICAL */} - {timelineMode === 'VERTICAL' ? ( - <TimelineVertical - activeTimelineItem={activeTimelineItem} - alternateCards={false} - autoScroll={handleScroll} - contentDetailsChildren={contentDetailsChildren} - hasFocus={hasFocus} - iconChildren={iconChildren} - items={items as TimelineCardModel[]} - mode={mode} - onClick={handleTimelineItemClick} - onElapsed={(itemId?: string) => - handleTimelineItemClick(itemId, true) - } - onOutlineSelection={onOutlineSelection} - slideShowRunning={slideShowRunning} - theme={theme} - enableOutline={enableOutline} - nestedCardHeight={nestedCardHeight} - /> - ) : null} - </TimelineMainWrapper> - - {/* placeholder to render timeline content for horizontal mode */} - <TimelineContentRender - id={id.current} - $showAllCards={showAllCardsHorizontal} - ref={horizontalContentRef} - /> - </Wrapper> - ); -}; - -Timeline.displayName = 'Timeline'; - -export default Timeline; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
index.tsx | -
-
- |
- 100% | -43/43 | -75% | -3/4 | -100% | -1/1 | -100% | -43/43 | -
toolbar.styles.ts | -
-
- |
- 100% | -33/33 | -100% | -1/1 | -100% | -0/0 | -100% | -33/33 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -11x -11x -11x -11x -11x -11x -11x -30x -30x -30x -30x -30x -30x -11x -11x -11x -11x -1x -1x - | import { FunctionComponent, ReactNode } from 'react';
-import {
- ContentWrapper,
- IconWrapper,
- ToolbarListItem,
- ToolbarWrapper,
-} from './toolbar.styles';
-import { Theme } from '@models/Theme';
-
-export type ToolbarItem = {
- icon?: ReactNode;
- id?: string;
- label?: string;
- name: string;
- onSelect: (id: string, name: string) => void;
-};
-
-export type ToolbarProps = {
- children?: ReactNode | ReactNode[];
- items?: ToolbarItem[];
- theme: Theme;
-};
-
-const Toolbar: FunctionComponent<ToolbarProps> = ({
- items,
- children = [],
- theme,
-}) => {
- return (
- <ToolbarWrapper theme={theme}>
- {items?.map(({ label, id, icon }, index) => {
- return (
- <ToolbarListItem aria-label={label} key={id}>
- {icon ? <IconWrapper>{icon}</IconWrapper> : null}
- <ContentWrapper>{children[index]}</ContentWrapper>
- </ToolbarListItem>
- );
- })}
- </ToolbarWrapper>
- );
-};
-
-export { Toolbar };
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled from 'styled-components'; - -export const ToolbarWrapper = styled.ul<{ theme: Theme }>` - list-style: none; - margin: 0; - display: flex; - align-items: center; - background-color: ${(p) => p.theme.toolbarBgColor}; - box-shadow: 0 2px 1px rgba(0, 0, 0, 0.1); - width: 100%; - height: 100%; - padding: 10px 20px; - border-radius: 6px; - flex-wrap: wrap; -`; - -export const ToolbarListItem = styled.li` - padding: 0; - margin: 0 0.5rem; -`; - -export const IconWrapper = styled.span` - display: flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; -`; - -export const ContentWrapper = styled.span` - display: flex; -`; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
components/common/styles | -
-
- |
- 100% | -19/19 | -100% | -3/3 | -100% | -2/2 | -100% | -19/19 | -
components/common/test | -
-
- |
- 100% | -83/83 | -100% | -1/1 | -14.28% | -1/7 | -100% | -83/83 | -
components/common/themes | -
-
- |
- 100% | -47/47 | -100% | -0/0 | -100% | -0/0 | -100% | -47/47 | -
components/effects | -
-
- |
- 62.01% | -111/179 | -75% | -12/16 | -75% | -3/4 | -62.01% | -111/179 | -
components/elements/list | -
-
- |
- 98.84% | -256/259 | -97.14% | -34/35 | -85.71% | -6/7 | -98.84% | -256/259 | -
components/elements/popover | -
-
- |
- 97.76% | -175/179 | -92.3% | -24/26 | -88.88% | -8/9 | -97.76% | -175/179 | -
components/timeline | -
-
- |
- 82.24% | -741/901 | -71.95% | -59/82 | -40.74% | -11/27 | -82.24% | -741/901 | -
components/timeline-elements/memoized | -
-
- |
- 68.39% | -119/174 | -80% | -12/15 | -100% | -1/1 | -68.39% | -119/174 | -
components/timeline-elements/timeline-card | -
-
- |
- 96.82% | -335/346 | -75% | -33/44 | -91.66% | -11/12 | -96.82% | -335/346 | -
components/timeline-elements/timeline-card-content | -
-
- |
- 81.91% | -1051/1283 | -66.25% | -106/160 | -83.33% | -30/36 | -81.91% | -1051/1283 | -
components/timeline-elements/timeline-card-media | -
-
- |
- 82.04% | -571/696 | -53.84% | -42/78 | -66.66% | -14/21 | -82.04% | -571/696 | -
components/timeline-elements/timeline-control | -
-
- |
- 98.1% | -310/316 | -53.19% | -25/47 | -100% | -3/3 | -98.1% | -310/316 | -
components/timeline-elements/timeline-item-title | -
-
- |
- 100% | -73/73 | -82.35% | -14/17 | -100% | -6/6 | -100% | -73/73 | -
components/timeline-horizontal | -
-
- |
- 100% | -138/138 | -55.55% | -5/9 | -100% | -1/1 | -100% | -138/138 | -
components/timeline-vertical | -
-
- |
- 92.97% | -635/683 | -59.09% | -39/66 | -90.9% | -10/11 | -92.97% | -635/683 | -
components/toolbar | -
-
- |
- 100% | -76/76 | -80% | -4/5 | -100% | -1/1 | -100% | -76/76 | -
utils | -
-
- |
- 81.81% | -72/88 | -63.63% | -7/11 | -75% | -6/8 | -81.81% | -72/88 | -