diff --git a/README.md b/README.md
index 0d0a322a..1563a084 100644
--- a/README.md
+++ b/README.md
@@ -164,7 +164,9 @@ Below are the available configuration options for the component:
| contentDetailsHeight | 150 | Controls the height of the details section if using cardDetailedText. Refer to [TimelineItem model](#timeline-item-model) for more info. |
| disableAutoScrollOnClick | false | Prevents auto-scrolling when a timeline card is clicked. |
| disableClickOnCircle | false | Disables the click action on circular points. |
+| disableInteraction | false | Disables all the interactions with the Timeline. |
| disableNavOnKey | false | Turns off keyboard navigation. |
+| disableTimelinePoint | false | Disables the timeline point in both `HORIZONTAL` and `VERTICAL` mode. |
| enableBreakPoint | true | Automatically switches to vertical mode when the vertical breakpoint is reached. |
| enableDarkToggle | false | Adds a toggle switch for dark mode. |
| enableOutline | false | Enables an outline menu in vertical and vertical alternating modes. |
@@ -172,8 +174,9 @@ Below are the available configuration options for the component:
| focusActiveItemOnLoad | false | Automatically scrolls to and focuses on the `activeItemIndex` when loading. |
| fontSizes | | Allows customization of font sizes. |
| hideControls | false | Hides navigation controls. |
-| itemWidth | 300 | Sets the width of the timeline section in horizontal mode. |
+| highlightCardsOnHover | false | Highlights the card on hover |
| items | [] | A collection of Timeline Item Models. |
+| itemWidth | 300 | Sets the width of the timeline section in horizontal mode. |
| lineWidth | 3px | Adjusts the width of the timeline track line. |
| mediaHeight | 200 | Sets the minimum height for media elements like images or videos in the card. |
| mediaSettings | | Configures settings specific to media layout. Refer to [mediaSettings](#media-settings) for more info. |
@@ -183,6 +186,7 @@ Below are the available configuration options for the component:
| onItemSelected | | Invokes a callback on item selection, passing relevant data. |
| onScrollEnd | | Detects the end of the timeline via `onScrollEnd`. |
| onThemeChange | | Invokes a callback when the theme changes, triggered via `enableDarkToggle`. |
+| parseDetailsAsHTML | false | Parses the `cardDetailedText` as HTML. |
| scrollable | true | Makes the timeline scrollable in `VERTICAL` and `VERTICAL_ALTERNATING` modes. |
| showAllCardsHorizontal | false | Displays all cards in horizontal mode. By default, only the active card is shown. |
| slideItemDuration | 5000 | Sets the duration (in milliseconds) that a timeline card is active during a slideshow. |
@@ -195,7 +199,6 @@ Below are the available configuration options for the component:
| uniqueId | | Used with `noUniqueId` to set a custom unique id for the wrapper. |
| useReadMore | true | Enables or disables the "read more" button. Available if text content on the card is taller than the card itself. |
| verticalBreakPoint | 768px | Sets the pixel count below which the timeline will switch to `VERTICAL` mode. |
-| disableTimelinePoint | false | Disables the timeline point in both `HORIZONTAL` and `VERTICAL` mode. |
### Mode
diff --git a/coverage/clover.xml b/coverage/clover.xml
deleted file mode 100644
index 7ee4596f..00000000
--- a/coverage/clover.xml
+++ /dev/null
@@ -1,5542 +0,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 -146 -147 -148 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -1x -1x -1x -1x - | /* eslint-disable react/prop-types */ -import { TimelineProps as PropsModel } from '@models/TimelineModel'; -import { - getDefaultButtonTexts, - getDefaultClassNames, - getDefaultThemeOrDark, - getSlideShowType, -} from '@utils/index'; -import { - createContext, - FunctionComponent, - useCallback, - useMemo, - useState, -} from 'react'; - -const GlobalContext = createContext< - PropsModel & { toggleDarkMode?: () => void } ->({}); - -type ContextProps = PropsModel & { - toggleDarkMode?: () => void; -}; - -const GlobalContextProvider: FunctionComponent<Partial<PropsModel>> = ( - props, -) => { - const { - cardHeight = 200, - cardLess = false, - flipLayout, - items = [], - theme, - buttonTexts, - classNames, - mode = 'VERTICAL_ALTERNATING', - fontSizes, - textOverlay, - darkMode, - slideShow, - onThemeChange, - mediaSettings, - mediaHeight = 200, - contentDetailsHeight = 10, - } = props; - - const [isDarkMode, setIsDarkMode] = useState(darkMode); - - const newCardHeight = useMemo( - () => Math.max(contentDetailsHeight || 0 + mediaHeight || 0, cardHeight), - [], - ); - - const newContentDetailsHeight = useMemo(() => { - const detailsHeightApprox = Math.round(newCardHeight * 0.75); - return contentDetailsHeight > newCardHeight - ? Math.min(contentDetailsHeight, detailsHeightApprox) - : Math.max(contentDetailsHeight, detailsHeightApprox); - }, [newCardHeight]); - - const toggleDarkMode = useCallback(() => { - setIsDarkMode(!isDarkMode); - onThemeChange?.(); - }, [isDarkMode]); - - const defaultProps = useMemo( - () => - Object.assign<ContextProps, ContextProps, ContextProps>( - {}, - { - borderLessCards: false, - cardHeight: newCardHeight, - cardLess: false, - disableAutoScrollOnClick: false, - disableClickOnCircle: false, - disableTimelinePoint: false, - enableBreakPoint: true, - enableDarkToggle: false, - focusActiveItemOnLoad: false, - lineWidth: 3, - mediaHeight: 200, - nestedCardHeight: 150, - scrollable: { - scrollbar: false, - }, - showAllCardsHorizontal: false, - showProgressOnSlideshow: slideShow, - slideItemDuration: 2000, - slideShowType: getSlideShowType(mode), - textOverlay: false, - timelinePointDimension: 16, - timelinePointShape: 'circle', - titleDateFormat: 'MMM DD, YYYY', - uniqueId: 'react-chrono', - useReadMore: true, - verticalBreakPoint: 1028, - }, - { - ...props, - activeItemIndex: flipLayout ? items?.length - 1 : 0, - buttonTexts: { - ...getDefaultButtonTexts(), - ...buttonTexts, - }, - cardHeight: cardLess ? cardHeight || 80 : cardHeight, - classNames: { - ...getDefaultClassNames(), - ...classNames, - }, - contentDetailsHeight: newContentDetailsHeight, - darkMode: isDarkMode, - fontSizes: { - cardSubtitle: '0.85rem', - cardText: '1rem', - cardTitle: '1rem', - title: '1rem', - ...fontSizes, - }, - mediaSettings: { - align: mode === 'VERTICAL' && !textOverlay ? 'left' : 'center', - imageFit: 'cover', - ...mediaSettings, - }, - theme: { - ...getDefaultThemeOrDark(isDarkMode), - ...theme, - }, - toggleDarkMode, - }, - ), - [newContentDetailsHeight, newCardHeight, isDarkMode, toggleDarkMode], - ); - - const { children } = props; - - return ( - <GlobalContext.Provider - value={{ ...defaultProps, darkMode: isDarkMode, toggleDarkMode }} - > - {children} - </GlobalContext.Provider> - ); -}; - -export default GlobalContextProvider; - -export { GlobalContext }; - |
- 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 -41x -41x -41x -41x -41x -41x -41x -41x - | 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% | -41/41 | -100% | -0/0 | -100% | -0/0 | -100% | -41/41 | -
- 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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', -}; - -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', -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
useMatchMedia.ts | -
-
- |
- 95.65% | -44/46 | -90% | -9/10 | -50% | -1/2 | -95.65% | -44/46 | -
useNewScrollPosition.ts | -
-
- |
- 100% | -88/88 | -95.65% | -22/23 | -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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -4x -4x -4x -4x -4x -4x -4x -3x -1x -1x -2x -2x -2x -2x -2x -3x -1x -1x -2x -2x -2x -2x -2x -2x -4x -4x -4x -4x - - -4x -4x -4x -4x - | /** - * 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 -22x -22x -22x -22x -22x -22x -22x -22x -22x -12x -12x -12x -12x -12x -12x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -2x -1x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -2x -6x -2x -2x -6x -6x -12x -22x -22x -22x -22x -22x -22x -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, setOffset] = 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)
- ) {
- setOffset(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) {
- setOffset(nOffset + Math.round(contentHeight / 2));
- } else if (notVisible) {
- setOffset(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. -
- -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 | 1x -1x -1x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -1x -1x -1x - | import React from 'react'; - -const ChevronLeft: React.FunctionComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - className="feather feather-chevron-left" - > - <polyline points="15 18 9 12 15 6"></polyline> - </svg> -); - -export default ChevronLeft; - |
- 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 | 1x -1x -1x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -7x -1x -1x -1x - | import React from 'react'; - -const ChevronRightIcon: React.FunctionComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - className="feather feather-chevron-right" - > - <polyline points="9 18 15 12 9 6"></polyline> - </svg> -); - -export default ChevronRightIcon; - |
- 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 | 1x -1x -1x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -1x -1x -1x - | import React from 'react'; - -const ChevronLeft: React.FunctionComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - className="feather feather-chevrons-left" - > - <polyline points="11 17 6 12 11 7"></polyline> - <polyline points="18 17 13 12 18 7"></polyline> - </svg> -); - -export default ChevronLeft; - |
- 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 | 1x -1x -1x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -1x -1x -1x - | import React from 'react'; - -const ChevronRightIcon: React.FunctionComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - className="feather feather-chevrons-right" - > - <polyline points="13 17 18 12 13 7"></polyline> - <polyline points="6 17 11 12 6 7"></polyline> - </svg> -); - -export default ChevronRightIcon; - |
- 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 | 1x -1x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -1x -1x - | import * as React from "react" - -function SvgComponent() { - return ( - <svg - xmlns="http://www.w3.org/2000/svg" - width={24} - height={24} - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - className="prefix__feather prefix__feather-x" - > - <path d="M18 6L6 18M6 6l12 12" /> - </svg> - ) -} - -export default SvgComponent - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
chev-left.tsx | -
-
- |
- 100% | -20/20 | -100% | -1/1 | -100% | -1/1 | -100% | -20/20 | -
chev-right.tsx | -
-
- |
- 100% | -20/20 | -100% | -1/1 | -100% | -1/1 | -100% | -20/20 | -
chevs-left.tsx | -
-
- |
- 100% | -21/21 | -100% | -1/1 | -100% | -1/1 | -100% | -21/21 | -
chevs-right.tsx | -
-
- |
- 100% | -21/21 | -100% | -1/1 | -100% | -1/1 | -100% | -21/21 | -
close.tsx | -
-
- |
- 100% | -22/22 | -100% | -1/1 | -100% | -1/1 | -100% | -22/22 | -
index.tsx | -
-
- |
- 100% | -9/9 | -100% | -0/0 | -100% | -0/0 | -100% | -9/9 | -
maximize.tsx | -
-
- |
- 100% | -17/17 | -100% | -1/1 | -100% | -1/1 | -100% | -17/17 | -
menu.tsx | -
-
- |
- 100% | -22/22 | -100% | -1/1 | -100% | -1/1 | -100% | -22/22 | -
minimize.tsx | -
-
- |
- 31.25% | -5/16 | -100% | -0/0 | -0% | -0/1 | -31.25% | -5/16 | -
minus.tsx | -
-
- |
- 100% | -16/16 | -100% | -1/1 | -100% | -1/1 | -100% | -16/16 | -
moon.tsx | -
-
- |
- 100% | -16/16 | -100% | -1/1 | -100% | -1/1 | -100% | -16/16 | -
plus.tsx | -
-
- |
- 31.25% | -5/16 | -100% | -0/0 | -0% | -0/1 | -31.25% | -5/16 | -
replay-icon.tsx | -
-
- |
- 100% | -19/19 | -100% | -1/1 | -100% | -1/1 | -100% | -19/19 | -
stop.tsx | -
-
- |
- 29.41% | -5/17 | -100% | -0/0 | -0% | -0/1 | -29.41% | -5/17 | -
sun.tsx | -
-
- |
- 27.77% | -5/18 | -100% | -0/0 | -0% | -0/1 | -27.77% | -5/18 | -
- 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x - | export { default as ChevronLeft } from './chev-left'; -export { default as ChevronRight } from './chev-right'; -export { default as MaximizeIcon } from './maximize'; -export { default as MinimizeIcon } from './minimize'; -export { default as MinusIcon } from './minus'; -export { default as MoonIcon } from './moon'; -export { default as PlusIcon } from './plus'; -export { default as StopIcon } from "./stop"; -export { default as SunIcon } from './sun'; - |
- 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 | 1x -1x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -1x -1x -1x - | -const SvgComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - className="feather feather-maximize-2" - > - <path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" /> - </svg> -) - -export default SvgComponent - |
- 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 | 1x -1x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -1x -1x - | import * as React from "react" - -function SvgComponent() { - return ( - <svg - xmlns="http://www.w3.org/2000/svg" - width={24} - height={24} - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - className="prefix__feather prefix__feather-menu" - > - <path d="M3 12h18M3 6h18M3 18h18" /> - </svg> - ) -} - -export default SvgComponent - |
- 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 | 1x -1x - - - - - - - - - - - -1x -1x -1x - | -const SvgComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - > - <path d="M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7" /> - </svg> -) - -export default SvgComponent - |
- 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 | 1x -1x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -1x -1x -1x - | -const SvgComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - > - <path d="M5 12h14" /> - </svg> -) - -export default SvgComponent - |
- 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 | 1x -1x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -1x -1x -1x - | -const SvgComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - viewBox="0 0 24 24" - > - <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /> - </svg> -) - -export default SvgComponent - |
- 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 | 1x -1x - - - - - - - - - - - -1x -1x -1x - | -const SvgComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - > - <path d="M12 5v14M5 12h14" /> - </svg> -) - -export default SvgComponent - |
- 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 React from 'react'; - -const ReplayIcon: React.FunctionComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - > - <polygon points="5 3 19 12 5 21 5 3"></polygon> - </svg> -); - -export default ReplayIcon; - |
- 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 | 1x -1x - - - - - - - - - - - - -1x -1x -1x - | -const SvgComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - viewBox="0 0 24 24" - > - <circle cx={12} cy={12} r={10} /> - <path d="M9 9h6v6H9z" /> - </svg> -) - -export default SvgComponent - |
- 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 | 1x -1x - - - - - - - - - - - - - -1x -1x -1x - | -const SvgComponent = () => ( - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth={2} - strokeLinecap="round" - strokeLinejoin="round" - className="feather feather-sun" - > - <circle cx={12} cy={12} r={5} /> - <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" /> - </svg> -) - -export default SvgComponent - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
- -File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
---|---|---|---|---|---|---|---|---|---|
GlobalContext.tsx | -
-
- |
- 19.72% | -29/147 | -100% | -0/0 | -0% | -0/1 | -19.72% | -29/147 | -
- 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.97% | -167/174 | -80.64% | -25/31 | -33.33% | -1/3 | -95.97% | -167/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 -23x -23x -23x -23x -23x -23x -23x -23x -23x -23x -23x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -3x -3x -3x -13x -13x -16x -16x -7x -23x -1x -1x -1x -1x -1x -21x -17x -17x -17x -17x -17x -17x -17x -17x -17x -17x -4x -1x - -1x -1x -1x -1x -1x -1x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x - -2x -1x -1x -1x -1x -1x -1x -1x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x - -2x -1x -1x -1x -1x -1x -1x -17x -17x -17x -17x -17x -17x -17x -17x -17x -6x -3x -3x -17x -17x -17x -17x -17x -17x -17x -16x -17x -1x -1x -17x -17x -17x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -14x -17x -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 -119 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 -13x -1x -1x -1x -1x -13x -13x -13x -13x -13x -13x -13x -13x -3x -3x -3x -3x - - - - -3x -3x -3x -3x -3x -3x -3x -3x -3x -10x -13x -13x -2x -2x -2x -2x -2x -2x -2x -2x -2x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -13x -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} - role="progressbar" - $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 -9x -9x -9x -9x -9x -9x -9x -3x -3x -3x -3x -3x -3x -3x -9x -9x -9x -3x -3x -3x -3x -3x -3x -9x -9x -9x -9x -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 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x -8x - -8x -8x -8x -8x -8x -8x -8x -8x -8x -1x -1x -1x -1x -1x - | import { ReactNode, forwardRef, useContext } from 'react'; -import { TimelineContentDetailsWrapper } from './timeline-card-content.styles'; -import { GlobalContext } from '../../GlobalContext'; -import { TimelineContentModel } from '@models/TimelineContentModel'; -import { getTextOrContent } from './text-or-content'; - -type DetailsTextProps = Pick< - TimelineContentModel, - 'detailedText' | 'timelineContent' -> & { - cardActualHeight?: number; - contentDetailsClass?: string; - customContent?: ReactNode; - detailsHeight?: number; - gradientColor?: string; - showMore?: boolean; -}; - -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 ? ( - 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 | -
-
- |
- 96.61% | -114/118 | -92.85% | -13/14 | -66.66% | -2/3 | -96.61% | -114/118 | -
content-header.tsx | -
-
- |
- 100% | -53/53 | -100% | -3/3 | -100% | -0/0 | -100% | -53/53 | -
details-text.tsx | -
-
- |
- 98.75% | -79/80 | -33.33% | -1/3 | -100% | -0/0 | -98.75% | -79/80 | -
text-or-content.tsx | -
-
- |
- 83.82% | -57/68 | -57.14% | -4/7 | -100% | -1/1 | -83.82% | -57/68 | -
timeline-card-content.styles.ts | -
-
- |
- 88.14% | -342/388 | -70.31% | -45/64 | -96.15% | -25/26 | -88.14% | -342/388 | -
timeline-card-content.tsx | -
-
- |
- 76.97% | -351/456 | -59.25% | -32/54 | -0% | -0/1 | -76.97% | -351/456 | -
- 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -16x -16x -16x -16x -16x -16x -16x -8x -8x -8x -8x -8x -8x - -8x -8x -8x - - - - - - - - - - -8x -8x -8x -8x -8x -6x -6x -6x -6x -6x -6x -6x -2x -8x -8x -16x -16x -16x -16x -16x -16x -1x -1x - | import { TimelineContentModel } from '@models/TimelineContentModel'; -import { ForwardRefExoticComponent, forwardRef, useContext } from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import { - TimelineSubContent, - TimelineContentDetails, -} from './timeline-card-content.styles'; - -export type TextOrContentModel = Pick< - TimelineContentModel, - 'timelineContent' | 'theme' | 'detailedText' -> & { - showMore?: boolean; -}; - -const getTextOrContent: ( - p: TextOrContentModel, -) => ForwardRefExoticComponent<TextOrContentModel> = ({ - timelineContent, - theme, - detailedText, - showMore, -}) => { - const TextOrContent = forwardRef<HTMLParagraphElement, TextOrContentModel>( - (prop, ref) => { - // const { timelineContent, theme, detailedText, showMore } = prop; - const isTextArray = Array.isArray(detailedText); - - const { fontSizes, classNames } = useContext(GlobalContext); - - if (timelineContent) { - return <div ref={ref}>{timelineContent}</div>; - } else { - let textContent = null; - if (isTextArray) { - textContent = (detailedText as string[]).map((text, index) => ( - <TimelineSubContent - key={index} - fontSize={fontSizes?.cardText} - className={classNames?.cardText} - theme={theme} - > - {text} - </TimelineSubContent> - )); - } else { - textContent = detailedText; - } - - return textContent ? ( - <TimelineContentDetails - className={showMore ? 'active' : ''} - ref={ref} - theme={theme} - > - {textContent} - </TimelineContentDetails> - ) : null; - } - }, - ); - - 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x - - -8x -8x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x - - - - - - - - - - - - - - - - - - - - - - - - -8x -1x -1x -8x - - - - - - -8x -8x - - - - -8x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -1x -1x -8x -8x -8x -8x -8x -8x -8x - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -8x -8x -8x -8x -8x -8x -8x - - - - - - - - - -8x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 -1x -1x -1x -1x -1x -2x -1x -1x -2x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -2x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -3x -2x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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; - $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: 4px; - 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; - position: relative; - text-align: left; - width: 98%; - z-index: 0; - - ${(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;` - : ''} - ${({ - $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; - - $${({ - 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.span<{ - $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; - - ${(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 | 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 -8x -20x -20x -20x -20x -20x -20x -8x -20x -20x -20x -8x -8x -8x -8x -8x -20x -20x -20x -8x -6x -6x -20x -20x -20x -20x -16x -8x -8x -8x -16x - - -8x -8x -8x -8x -8x -8x -16x -20x -20x -20x -20x - - - - - - - - - - - - - - - - - - -20x -20x -20x -8x - - -20x -20x -20x -20x - - - - - - - - - - - - - -20x -20x -20x -20x - - - - - - - - - - - - - -20x -20x -20x -8x - - -8x -8x - - - -8x -8x -8x - - -8x -8x -8x -8x -20x -20x -20x -8x - - -20x -20x -20x -8x - - -20x -20x -20x -8x -20x -20x -20x -20x -20x -20x -8x -20x -20x -20x -20x -20x -20x -20x -20x - - - - - - - - - - - -20x -20x -20x -20x -20x -8x -8x -8x -8x -20x -20x -20x -20x -20x -8x -8x -8x - -8x -8x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -8x - -8x -8x -8x - - -20x -20x -20x - - - - -20x -20x -20x -8x - - - - - - -8x -20x -20x -20x -20x -20x -20x -20x -20x -8x -8x - -8x - -8x -20x -20x -20x -20x -8x -20x -20x -20x -8x -8x -8x -8x -8x -8x -20x -20x -20x -8x -8x -8x - - - - - - - - - - -8x -8x -8x -8x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -20x -18x -18x -18x -18x -18x -18x -18x -2x -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 - | 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 { TimelineItemContentWrapper } from './timeline-card-content.styles'; -import { getTextOrContent } from './text-or-content'; -import { DetailsText } from './details-text'; - -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<HTMLDivElement | 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, - } = 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) { - 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} - > - {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% | -1/1 | -100% | -0/0 | -100% | -48/48 | -
timeline-card-media.styles.ts | -
-
- |
- 92.43% | -220/238 | -80% | -44/55 | -100% | -17/17 | -92.43% | -220/238 | -
timeline-card-media.tsx | -
-
- |
- 86.82% | -356/410 | -73.46% | -36/49 | -25% | -1/4 | -86.82% | -356/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 -16x -1x -1x -1x -16x -15x - - - -15x -15x -15x -15x -15x -15x -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 -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -16x -16x -16x -16x -16x -16x -16x -1x -1x -1x -1x -1x -16x - - - - - - -16x -16x - - - - - - -16x -16x -2x -2x -2x -2x -2x -2x -16x -1x -1x -16x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -14x -14x -1x -1x -1x -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 -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -3x -1x -1x -1x -3x -2x -2x -2x -2x -3x -1x -1x -3x -3x - - -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 -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x - - -16x -16x -7x -7x -16x -9x -9x -9x -16x -16x -16x -16x - -16x -16x -16x -16x - - - - - - - - -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x - - - - - -16x -16x - - - - - -16x -16x - - - - - -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -2x - - -16x -16x -16x -16x -16x - - - - - -16x -16x -16x -16x -16x -16x -16x -16x - - - - - -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -2x -16x -14x -14x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -14x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x - -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -16x -2x - - - - -16x -16x -16x -14x -14x - - -16x -16x -16x - - - - - - - - - -16x -16x -16x -2x -2x -2x -2x -2x -2x -2x -14x -16x -16x -16x -16x -16x -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} - /> - ); - - // 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} - role="img" - fit={mediaSettings?.imageFit} - /> - ); - }, [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} - role="progressbar" - $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.41% | -188/195 | -60% | -9/15 | -66.66% | -2/3 | -96.41% | -188/195 | -
- 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 -18x -18x -18x - - - - -18x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x - - - - -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x - - - -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -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, - } = 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} - $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} - 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% | -85/85 | -87.5% | -7/8 | -100% | -2/2 | -100% | -85/85 | -
timeline-control.tsx | -
-
- |
- 97.2% | -209/215 | -32.35% | -11/34 | -100% | -1/1 | -97.2% | -209/215 | -
- 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 -1x -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 -16x -16x -21x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 }>`
- background: rgba(229, 229, 229, 0.85);
- border-radius: 25px;
- display: flex;
- list-style: none;
- padding: 0.25em 0.25em;
-`;
-
-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.7)' : '')};
-`;
-
-export const TimelineNavButton = styled.button<{
- mode?: TimelineMode;
- rotate?: 'TRUE' | 'FALSE';
- theme?: Theme;
-}>`
- align-items: center;
- background: ${(p) => p.theme.primary};
- border-radius: 50%;
- border: 0;
- color: #fff;
- cursor: pointer;
- display: flex;
- filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.25));
- height: 20px;
- justify-content: center;
- margin: 0 0.2em;
- padding: 0;
- transition: all 0.1s ease-in;
- width: 20px;
-
- 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: 80%;
- height: 80%;
- }
-`;
-
-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: 2em;
- justify-content: center;
- margin-left: 0.5em;
- width: 2em;
- 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x - - - - - -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x -4x - -4x -4x -4x -4x -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 } = - 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)} - > - {/* jump to first */} - <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 | -85.71% | -12/14 | -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 -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 -13x -13x -13x -13x -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-outline-item-list.tsx | -
-
- |
- 100% | -49/49 | -100% | -3/3 | -100% | -2/2 | -100% | -49/49 | -
timeline-outline.styles.ts | -
-
- |
- 99.39% | -164/165 | -92.59% | -25/27 | -100% | -7/7 | -99.39% | -164/165 | -
timeline-outline.tsx | -
-
- |
- 99.09% | -109/110 | -80% | -12/15 | -100% | -1/1 | -99.09% | -109/110 | -
- 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 -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -3x -3x -3x -3x -3x -3x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -6x -3x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import { - List, - ListItem, - ListItemBullet, - ListItemName, -} from './timeline-outline.styles'; -import { FunctionComponent } from 'react'; -import { TimelineOutlineItem } from './timeline-outline'; - -interface OutlineItemListModel { - handleSelection: (index: number, id?: string) => void; - items: TimelineOutlineItem[]; - theme: Theme; -} - -/** - * OutlineItemList component - * This component is responsible for rendering the outline list of items. - * It takes a list of items, a theme, and a selection handler function as props, - * and maps through the items to render each one within the list. - * - * @property {TimelineOutlineItem[]} items - The items to be displayed in the list. - * @property {Theme} theme - The theme object, used for styling. - * @property {function} handleSelection - The callback to be invoked when an item is selected. - * @returns {JSX.Element} The rendered OutlineItemList component. - */ -const OutlineItemList: FunctionComponent<OutlineItemListModel> = ({ - items, - handleSelection, - theme, -}) => ( - <List role="list"> - {items.map((item, index) => ( - <ListItem - key={item.id} - onPointerDown={() => handleSelection(index, item.id)} - role="listitem" - > - <ListItemBullet theme={theme} selected={item.selected}></ListItemBullet> - <ListItemName theme={theme} selected={item.selected}> - {item.name} - </ListItemName> - </ListItem> - ))} - </List> -); - -export { OutlineItemList }; - |
- 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 -6x -4x -4x -4x -4x -1x -1x -1x -1x -1x -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 - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -6x -2x -1x -1x -1x - | import { Theme } from '@models/Theme'; -import styled, { keyframes } from 'styled-components'; -import { OutlinePosition } from './timeline-outline'; - -const open = keyframes` - from { - width: 30px; - height: 30px; - } - - to: { - width: 200px; - height: 50%; - } - `; - -const close = keyframes` - from { - width: 200px; - height: 50%; - } - - to: { - width: 30px; - height: 30px; - } -`; - -export const OutlineWrapper = styled.div<{ - open?: boolean; - position?: OutlinePosition; -}>` - animation: ${(p) => (p.open ? open : close)}; - animation-duration: 0.2s; - animation-timing-function: ease-in; - background: rgba(255, 255, 255, 0.98); - border: 1px solid ${(p) => (p.open ? '#f5f5f5' : 'none')}; - height: 50%; - position: absolute; - top: 1rem; - width: 100%; - z-index: 9000; - ${(p) => - p.position === OutlinePosition.left ? `left: 1rem;` : `right: 3rem;`}; - ${(p) => - p.open - ? ` - width: 200px; - height: 50%; - box-shadow: 0 5px 10px 2px rgba(0,0,0,0.2); - overflow-y: auto;` - : `width: 30px; height: 30px;`}; -`; - -export const OutlinePane = styled.aside<{ open?: boolean }>` - align-items: center; - border-radius: 4px; - display: flex; - justify-content: center; -`; - -export const OutlineButton = styled.button<{ - open?: boolean; - position?: OutlinePosition; - theme?: Theme; -}>` - align-items: center; - align-self: flex-end; - background: #fff; - border-radius: 4px; - border: 0; - box-shadow: ${(p) => (!p.open ? '0 0 10px 2px rgba(0,0,0,0.2)' : 'none')}; - cursor: pointer; - display: flex; - height: 30px; - justify-content: center; - padding: 0; - width: 30px; - - ${(p) => - p.position === OutlinePosition.left - ? 'margin-right: auto;' - : 'margin-left: auto;'}; - - & svg { - width: 70%; - height: 70%; - } - - & svg path { - color: ${(p) => p.theme.primary}; - } -`; - -export const List = styled.ul` - display: flex; - flex-direction: column; - height: 100%; - list-style: none; - margin: 0; - overflow-y: auto; - padding: 0; - width: 80%; -`; - -export const ListItem = styled.li` - align-items: center; - display: flex; - font-size: 0.9rem; - justify-content: flex-start; - margin: 0.75rem 0; - cursor: pointer; - position: relative; - - &:not(:last-child)::after { - content: ''; - display: block; - width: 100%; - position: absolute; - height: 1px; - background: #ddd; - left: 0; - right: 0; - margin: 0 auto; - bottom: -50%; - } -`; - -export const ListItemName = styled.span<{ selected?: boolean; theme?: Theme }>` - font-size: 0.75rem; - color: ${(p) => (p.selected ? p.theme.primary : '')}; - padding-left: 0.25rem; - - &:hover { - color: ${(p) => p.theme.primary}; - } -`; - -export const ListItemBullet = styled.span<{ - selected?: boolean; - theme?: Theme; -}>` - align-items: center; - display: flex; - justify-content: center; - margin-right: 1rem; - position: relative; - - &::after { - content: ''; - display: block; - position: absolute; - width: 8px; - height: 8px; - border-radius: 50%; - background: ${(p) => - p.selected ? `${p.theme.secondary}` : `${p.theme.primary}`}; - left: 0; - margin: 0 auto; - border: ${(p) => - p.selected - ? `2px solid ${p.theme.secondary}` - : `2px solid ${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 -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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 -4x -4x - -10x -10x -10x -10x -7x -3x -7x -4x -4x -10x -10x -10x -10x -1x -1x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -10x -3x -3x -3x -3x -3x -10x -10x -10x -10x -10x -1x -1x - | import { Theme } from '@models/Theme'; -import { TimelineMode } from '@models/TimelineModel'; -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; -import { GlobalContext } from '../../GlobalContext'; -import CloseIcon from '../../icons/close'; -import MenuIcon from '../../icons/menu'; -import { - OutlineButton, - OutlinePane, - OutlineWrapper, -} from './timeline-outline.styles'; -import { OutlineItemList } from './timeline-outline-item-list'; - -export enum OutlinePosition { - 'left', - 'right', -} - -export interface TimelineOutlineModel { - items?: TimelineOutlineItem[]; - mode?: TimelineMode; - onSelect?: (index: number) => void; - theme?: Theme; -} - -export interface TimelineOutlineItem { - id?: string; - name?: string; - selected?: boolean; -} - -/** - * TimelineOutline component - * This component renders the outline pane of a timeline, including a list of items and corresponding selection functionality. - * It provides an interface to toggle the outline pane and select items within the timeline. - * The component leverages memoization to prevent unnecessary re-renders and optimizes the rendering process. - * - * @property {TimelineOutlineItem[]} items - The items to be displayed in the outline. - * @property {TimelineMode} mode - The mode of the timeline which determines the outline position. - * @property {function} onSelect - The callback to be invoked when an item is selected. - * @property {Theme} theme - The theme object, used for styling. - * @returns {JSX.Element} The TimelineOutline component. - */ -const TimelineOutline: React.FC<TimelineOutlineModel> = ({ - items = [], - onSelect, - mode, - theme, -}: TimelineOutlineModel) => { - const [openPane, setOpenPane] = useState(false); - const [showList, setShowList] = useState(false); - - const { theme: globalTheme } = useContext(GlobalContext); - const mergedTheme = theme || globalTheme; - - const togglePane = useCallback(() => setOpenPane((prev) => !prev), []); - - const position = useMemo( - () => - mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING' - ? OutlinePosition.right - : OutlinePosition.left, - [mode], - ); - - useEffect(() => { - if (openPane) { - setShowList(true); - } else { - setShowList(false); - } - }, [openPane]); - - const handleSelection = useCallback( - (index: number, id?: string) => { - if (onSelect) onSelect(index); - }, - [onSelect], - ); - - return ( - <OutlineWrapper position={position} open={openPane}> - <OutlineButton - onPointerDown={togglePane} - theme={mergedTheme} - open={openPane} - position={position} - > - {openPane ? <CloseIcon /> : <MenuIcon />} - </OutlineButton> - <OutlinePane open={openPane}> - {showList && ( - <OutlineItemList - items={items} - handleSelection={handleSelection} - theme={mergedTheme} - /> - )} - </OutlinePane> - </OutlineWrapper> - ); -}; - -export { TimelineOutline }; - |
- 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 | -100% | -0/0 | -100% | -0/0 | -100% | -35/35 | -
timeline-horizontal.tsx | -
-
- |
- 28.71% | -29/101 | -100% | -0/0 | -0% | -0/1 | -28.71% | -29/101 | -
- 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 | 1x -1x -1x -1x -1x -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 { 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); - - // 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.75% | -128/138 | -60% | -6/10 | -0% | -0/1 | -92.75% | -128/138 | -
timeline-vertical-item.tsx | -
-
- |
- 96.17% | -226/235 | -33.33% | -4/12 | -100% | -1/1 | -96.17% | -226/235 | -
timeline-vertical-shape.styles.ts | -
-
- |
- 100% | -51/51 | -92.3% | -12/13 | -100% | -4/4 | -100% | -51/51 | -
timeline-vertical.styles.ts | -
-
- |
- 84.61% | -121/143 | -42.1% | -8/19 | -100% | -5/5 | -84.61% | -121/143 | -
timeline-vertical.tsx | -
-
- |
- 24% | -30/125 | -100% | -0/0 | -0% | -0/1 | -24% | -30/125 | -
- 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 | 1x -1x -1x -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 -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 - - - - - -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 -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -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<HTMLDivElement>(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" - role="button" - $cardLess={cardLess} - > - {/* {!disableTimelinePoint ? ( */} - <TimelinePointContainer - className={`${className} timeline-vertical-circle`} - {...clickHandlerProps} - ref={circleRef} - role="button" - 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 -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x - - - - - -2x -2x -2x -2x -2x - - - -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x - -2x -2x -2x -2x -2x -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<HTMLDivElement>(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, - } = 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={flipLayout} - > - <TimelineItemTitle - title={title} - active={active} - 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} - role="listitem" - $isNested={isNested} - theme={theme} - > - {/* title */} - {!isNested ? Title : null} - - {/* card section */} - <TimelineCardContentWrapper - className={contentClass} - $alternateCards={alternateCards} - $noTitle={!title} - $flip={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={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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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: ${(p) => (p.$cardLess ? '5%' : '10%')};
-
- &.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.div<{ $hide?: boolean }>`
- position: relative;
- z-index: 1;
- visibility: ${(p) => (p.$hide ? 'hidden' : '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 -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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -2x - -2x - -2x -2x -2x -2x -1x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x -2x - - - - - - - - - - -2x -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 -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.div<{ - $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; - - &.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: 75%;`; - } - }} - ${(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: 15%')}; - - &.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 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 | 1x -1x -1x -1x -1x -1x -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 { TimelineVerticalModel } from '@models/TimelineVerticalModel'; -import React, { useCallback, useMemo } from 'react'; -import { TimelineOutline } from '../timeline-elements/timeline-outline/timeline-outline'; -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(() => {}, []); - - const outlineItems = useMemo( - () => - items.map((item) => ({ - id: Math.random().toString(16).slice(2), - name: item.title, - })), - [items], - ); - - 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.style.ts | -
-
- |
- 81.87% | -122/149 | -100% | -0/0 | -0% | -0/3 | -81.87% | -122/149 | -
timeline.tsx | -
-
- |
- 7.17% | -34/474 | -100% | -0/0 | -0% | -0/1 | -7.17% | -34/474 | -
- 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x - - - - - - - - - - - - -1x -1x -1x - - - - - - - - - -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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; - 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%; - - ${ScrollBar} - - &.horizontal { - min-height: 150px; - } - - padding: ${({ $scrollable }) => (!$scrollable ? '0 1rem 0' : '')}; -`; - -export const TimelineMain = styled.div` - align-items: center; - display: flex; - left: 0; - top: 50%; - position: absolute; - transition: all 0.2s ease; - transform: translate(0, -50%); - - &.vertical { - align-items: flex-start; - height: 100%; - justify-content: flex-start; - width: 100%; - } -`; - -export const Outline = styled.div<{ color?: string; height?: number }>` - background: ${(p) => p.color}; - height: ${(p) => `${p.height}px`}; - left: 0; - margin-left: auto; - margin-right: auto; - position: absolute; - right: 0; - width: 100%; -`; - -export const TimelineControlContainer = styled.div<{ - active?: boolean; - mode?: TimelineMode; -}>` - align-items: center; - display: flex; - 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 }>` - margin-left: auto; - margin-right: auto; - width: 98%; - display: flex; - align-items: flex-start; - justify-content: ${(p) => (p.$showAllCards ? 'flex-start' : 'center')}; - overflow-x: hidden; -`; - |
- 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 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -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 { Scroll } from '@models/TimelineHorizontalModel'; -import { TimelineCardModel } from '@models/TimelineItemModel'; -import { TimelineModel } from '@models/TimelineModel'; -import { uniqueID as genUniqueID } from '@utils/index'; -import cls from 'classnames'; -import 'focus-visible'; -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 TimelineControl from '../timeline-elements/timeline-control/timeline-control'; -import TimelineHorizontal from '../timeline-horizontal/timeline-horizontal'; -import TimelineVertical from '../timeline-vertical/timeline-vertical'; -import { - Outline, - TimelineContentRender, - TimelineControlContainer, - TimelineMain, - TimelineMainWrapper, - 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, - } = 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); - - 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 : genUniqueID()}`, - ); - - useMatchMedia( - `(min-width: 100px) and (max-width: ${verticalBreakPoint}px)`, - () => { - if (mode === 'VERTICAL_ALTERNATING') { - setTimelineMode('VERTICAL'); - } - }, - enableBreakPoint, - ); - - useMatchMedia( - `(min-width: ${verticalBreakPoint + 1}px)`, - () => { - setTimelineMode(mode); - }, - enableBreakPoint, - ); - - // handlers for navigation - const handleNext = useCallback(() => { - hasFocus && onNext?.(); - }, [hasFocus, onNext]); - - const handlePrevious = useCallback( - () => hasFocus && onPrevious?.(), - [hasFocus, onPrevious], - ); - - const handleFirst = useCallback(() => { - hasFocus && onFirst?.(); - }, [hasFocus, onFirst]); - - const handleLast = useCallback( - () => hasFocus && 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; - } - } - - // const selectedItem = items.find((item) => item.id === itemId); - - // if (selectedItem) { - // onItemSelected?.(selectedItem); - // } - } - }; - - useEffect(() => { - const activeItem = items[activeTimelineItem || 0]; - - 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]); - - 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 wrapperClass = useMemo(() => { - return cls(mode.toLocaleLowerCase(), { - 'focus-visible': !isChild, - 'js-focus-visible': !isChild, - }); - }, [mode, isChild]); - - return ( - <Wrapper - // tabIndex={0} - onKeyDown={handleKeyDown} - className={wrapperClass} - cardPositionHorizontal={cardPositionHorizontal} - onMouseDown={() => { - setHasFocus(true); - }} - $hideControls={hideControls} - onKeyUp={(evt) => { - if (evt.key === 'Escape') { - onPaused?.(); - } - }} - > - <TimelineMainWrapper - ref={timelineMainRef} - $scrollable={canScrollTimeline} - className={`${mode.toLowerCase()} timeline-main-wrapper`} - id="timeline-main-wrapper" - theme={theme} - mode={mode} - 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' ? ( - <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> - - {/* Timeline Controls */} - {!hideControls && ( - <TimelineControlContainer mode={mode}> - <TimelineControl - disableLeft={ - flipLayout - ? activeTimelineItem === items.length - 1 - : activeTimelineItem === 0 - } - disableRight={ - flipLayout - ? activeTimelineItem === 0 - : activeTimelineItem === items.length - 1 - } - id={id.current} - onFirst={handleFirst} - onLast={handleLast} - onNext={handleNext} - onPrevious={handlePrevious} - onReplay={onRestartSlideshow} - slideShowEnabled={slideShowEnabled} - slideShowRunning={slideShowRunning} - isDark={darkMode} - onToggleDarkMode={toggleDarkMode} - onPaused={onPaused} - /> - </TimelineControlContainer> - )} - - {/* 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 | -- |
---|---|---|---|---|---|---|---|---|---|
components | -
-
- |
- 19.72% | -29/147 | -100% | -0/0 | -0% | -0/1 | -19.72% | -29/147 | -
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% | -41/41 | -100% | -0/0 | -100% | -0/0 | -100% | -41/41 | -
components/effects | -
-
- |
- 98.5% | -132/134 | -93.93% | -31/33 | -66.66% | -2/3 | -98.5% | -132/134 | -
components/icons | -
-
- |
- 82.59% | -223/270 | -100% | -10/10 | -71.42% | -10/14 | -82.59% | -223/270 | -
components/timeline | -
-
- |
- 25.04% | -156/623 | -100% | -0/0 | -0% | -0/4 | -25.04% | -156/623 | -
components/timeline-elements/memoized | -
-
- |
- 95.97% | -167/174 | -80.64% | -25/31 | -33.33% | -1/3 | -95.97% | -167/174 | -
components/timeline-elements/timeline-card | -
-
- |
- 96.81% | -334/345 | -62.16% | -23/37 | -91.66% | -11/12 | -96.81% | -334/345 | -
components/timeline-elements/timeline-card-content | -
-
- |
- 86.17% | -1041/1208 | -67.58% | -98/145 | -90.32% | -28/31 | -86.17% | -1041/1208 | -
components/timeline-elements/timeline-card-media | -
-
- |
- 89.65% | -624/696 | -77.14% | -81/105 | -85.71% | -18/21 | -89.65% | -624/696 | -
components/timeline-elements/timeline-control | -
-
- |
- 98% | -294/300 | -42.85% | -18/42 | -100% | -3/3 | -98% | -294/300 | -
components/timeline-elements/timeline-item-title | -
-
- |
- 100% | -73/73 | -88.88% | -16/18 | -100% | -6/6 | -100% | -73/73 | -
components/timeline-elements/timeline-outline | -
-
- |
- 99.38% | -322/324 | -88.88% | -40/45 | -100% | -10/10 | -99.38% | -322/324 | -
components/timeline-horizontal | -
-
- |
- 47.05% | -64/136 | -100% | -0/0 | -0% | -0/1 | -47.05% | -64/136 | -
components/timeline-vertical | -
-
- |
- 80.34% | -556/692 | -55.55% | -30/54 | -83.33% | -10/12 | -80.34% | -556/692 | -
utils | -
-
- |
- 100% | -67/67 | -100% | -14/14 | -100% | -6/6 | -100% | -67/67 | -