diff --git a/coverage/.tmp/coverage-12.json b/coverage/.tmp/coverage-12.json new file mode 100644 index 00000000..222a9436 --- /dev/null +++ b/coverage/.tmp/coverage-12.json @@ -0,0 +1 @@ +{"result":[{"scriptId":"1081","url":"file:///C:/Users/prabh/Github/react-chrono/src/test-setup.js","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":1884,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":13,"endOffset":1884,"count":1}],"isBlockCoverage":true}]},{"scriptId":"1356","url":"file:///C:/Users/prabh/Github/react-chrono/src/utils/utils.test.ts","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":8240,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":13,"endOffset":8240,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":821,"endOffset":3230,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":908,"endOffset":1000,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1070,"endOffset":1196,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1294,"endOffset":1409,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1524,"endOffset":1746,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":1834,"endOffset":2138,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":2228,"endOffset":2548,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":2653,"endOffset":2824,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":2935,"endOffset":3047,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":3134,"endOffset":3226,"count":1}],"isBlockCoverage":true}]},{"scriptId":"1357","url":"file:///C:/Users/prabh/Github/react-chrono/src/utils/index.ts","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":7738,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":13,"endOffset":7738,"count":1}],"isBlockCoverage":true},{"functionName":"uniqueID","ranges":[{"startOffset":530,"endOffset":763,"count":1},{"startOffset":669,"endOffset":744,"count":7}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":861,"endOffset":885,"count":1}],"isBlockCoverage":true},{"functionName":"hexToRGBA","ranges":[{"startOffset":907,"endOffset":1100,"count":1}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":1199,"endOffset":1224,"count":1}],"isBlockCoverage":true},{"functionName":"getDefaultThemeOrDark","ranges":[{"startOffset":1258,"endOffset":1382,"count":3},{"startOffset":1286,"endOffset":1335,"count":1},{"startOffset":1335,"endOffset":1381,"count":2}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":1493,"endOffset":1530,"count":3}],"isBlockCoverage":true},{"functionName":"getDefaultClassNames","ranges":[{"startOffset":1563,"endOffset":1764,"count":1}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":1874,"endOffset":1910,"count":1}],"isBlockCoverage":true},{"functionName":"getDefaultButtonTexts","ranges":[{"startOffset":1944,"endOffset":2156,"count":1}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":2267,"endOffset":2304,"count":1}],"isBlockCoverage":true},{"functionName":"getSlideShowType","ranges":[{"startOffset":2333,"endOffset":2551,"count":4},{"startOffset":2374,"endOffset":2400,"count":1},{"startOffset":2400,"endOffset":2428,"count":3},{"startOffset":2428,"endOffset":2454,"count":1},{"startOffset":2454,"endOffset":2494,"count":2},{"startOffset":2494,"endOffset":2550,"count":1}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":2657,"endOffset":2689,"count":4}],"isBlockCoverage":true},{"functionName":"isTextArray","ranges":[{"startOffset":2713,"endOffset":2756,"count":0}],"isBlockCoverage":false},{"functionName":"get","ranges":[{"startOffset":2857,"endOffset":2884,"count":0}],"isBlockCoverage":false},{"functionName":"sanitizeHtmlText","ranges":[{"startOffset":2913,"endOffset":3065,"count":0}],"isBlockCoverage":false},{"functionName":"get","ranges":[{"startOffset":3171,"endOffset":3203,"count":0}],"isBlockCoverage":false}]},{"scriptId":"1358","url":"file:///C:/Users/prabh/Github/react-chrono/src/components/common/themes/index.ts","functions":[{"functionName":"","ranges":[{"startOffset":0,"endOffset":4271,"count":1}],"isBlockCoverage":true},{"functionName":"","ranges":[{"startOffset":13,"endOffset":4271,"count":1}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":824,"endOffset":852,"count":4}],"isBlockCoverage":true},{"functionName":"get","ranges":[{"startOffset":1507,"endOffset":1532,"count":2}],"isBlockCoverage":true}]}]} \ No newline at end of file diff --git a/coverage/lcov-report/react-chrono/index.html b/coverage/lcov-report/react-chrono/index.html new file mode 100644 index 00000000..971e78d9 --- /dev/null +++ b/coverage/lcov-report/react-chrono/index.html @@ -0,0 +1,131 @@ + + + + +
++ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
postcss.config.js | +
+
+ |
+ 0% | +0/5 | +0% | +0/1 | +0% | +0/1 | +0% | +0/5 | +
tailwind.config.js | +
+
+ |
+ 0% | +0/8 | +0% | +0/1 | +0% | +0/1 | +0% | +0/8 | +
+ 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 | + + + + + | module.exports = { + plugins: { + autoprefixer: {}, + }, +}; + |
+ 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 + | /* eslint-disable react/prop-types */ +import { TimelineProps as PropsModel } from '@models/TimelineModel'; +import { + getDefaultButtonTexts, + getDefaultClassNames, + getDefaultThemeOrDark, + getSlideShowType, +} from '@utils/index'; +import { + FunctionComponent, + createContext, + 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 || props.disableInteraction, + disableClickOnCircle: false || props.disableInteraction, + disableInteraction: false, + disableTimelinePoint: false || props.disableInteraction, + enableBreakPoint: true, + enableDarkToggle: false, + focusActiveItemOnLoad: false, + highlightCardsOnHover: false, + lineWidth: 3, + mediaHeight: 200, + nestedCardHeight: 150, + parseDetailsTextHTML: false, + 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 | +
triangle-right.tsx | +
+
+ |
+ 0% | +0/11 | +0% | +0/1 | +0% | +0/1 | +0% | +0/11 | +
+ 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. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 | + + + + + + + + + + + | import * as React from "react" + +function SvgComponent() { + return ( + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 490 490"> + <path d="M.062 128.25L383.812 512 .062 895.75v-767.5z" /> + </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.33% | +29/150 | +100% | +0/0 | +0% | +0/1 | +19.33% | +29/150 | +
index.tsx | +
+
+ |
+ 0% | +0/222 | +0% | +0/1 | +0% | +0/1 | +0% | +0/222 | +
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { TimelineItemModel } from '@models/TimelineItemModel'; +import { TimelineProps } from '@models/TimelineModel'; +import dayjs from 'dayjs'; +import 'focus-visible'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import GlobalContextProvider from './GlobalContext'; +import Timeline from './timeline/timeline'; +const toReactArray = React.Children.toArray; + +const Chrono: React.FunctionComponent<Partial<TimelineProps>> = ( + props: Partial<TimelineProps>, +) => { + const { + allowDynamicUpdate = false, + children, + items, + onScrollEnd, + slideShow = false, + onItemSelected, + activeItemIndex = 0, + titleDateFormat = 'MMM DD, YYYY', + mode, + enableOutline, + hideControls, + } = props; + + const [timeLineItems, setItems] = useState<TimelineItemModel[]>([]); + const timeLineItemsRef = useRef<TimelineItemModel[]>(); + const [slideShowActive, setSlideshowActive] = useState(false); + const [activeTimelineItem, setActiveTimelineItem] = useState(activeItemIndex); + + const initItems = (lineItems?: TimelineItemModel[]): TimelineItemModel[] => { + if (lineItems && lineItems.length) { + return lineItems.map((item, index) => { + const id = Math.random().toString(16).slice(2); + + return { + ...item, + _dayjs: dayjs(item.date), + active: index === activeItemIndex, + id, + items: item.items?.map((subItem) => ({ + ...subItem, + _dayjs: dayjs(subItem.date), + id: Math.random().toString(16).slice(2), + isNested: true, + visible: true, + })), + title: item.date + ? dayjs(item.date).format(titleDateFormat) + : item.title, + visible: true, + }; + }); + } + + const itemLength = React.Children.toArray(children).filter( + (item) => (item as React.ReactElement).props.className !== 'chrono-icons', + ).length; + + return Array.from({ length: itemLength }).map((_, index) => ({ + active: index === activeItemIndex, + id: Math.random().toString(16).slice(2), + visible: true, + })); + }; + + const updateItems = (lineItems: TimelineItemModel[]) => { + if (lineItems) { + const pos = timeLineItems.length; + + return lineItems.map((item, index) => ({ + ...item, + active: index === pos, + // id: Math.random().toString(16).slice(2), + visible: true, + })); + } else { + return []; + } + }; + + useEffect(() => { + const _items = items?.filter((item) => item); + let newItems: TimelineItemModel[] = []; + + if (!_items?.length) { + const lineItems = initItems(); + setItems(lineItems); + return; + } + + if (timeLineItems.length && _items.length > timeLineItems.length) { + newItems = updateItems(_items); + } else if (_items.length) { + newItems = initItems(_items); + } + + if (newItems.length) { + timeLineItemsRef.current = newItems; + setItems(newItems); + } + }, [JSON.stringify(allowDynamicUpdate ? items : null)]); + + const handleTimelineUpdate = useCallback((actvTimelineIndex: number) => { + setItems((lineItems) => + lineItems.map((item, index) => ({ + ...item, + active: index === actvTimelineIndex, + visible: actvTimelineIndex >= 0, + })), + ); + + setActiveTimelineItem(actvTimelineIndex); + + if (items) { + if (items.length - 1 === actvTimelineIndex) { + setSlideshowActive(false); + } + } + }, []); + + useEffect(() => { + handleTimelineUpdate(activeItemIndex); + }, [activeItemIndex]); + + const restartSlideShow = useCallback(() => { + handleTimelineUpdate(-1); + + setTimeout(() => { + setSlideshowActive(true); + handleTimelineUpdate(0); + }, 0); + }, []); + + const handleOnNext = () => { + if (!timeLineItems.length) { + return; + } + if (activeTimelineItem < timeLineItems.length - 1) { + const newTimeLineItem = activeTimelineItem + 1; + + handleTimelineUpdate(newTimeLineItem); + setActiveTimelineItem(newTimeLineItem); + } + }; + + const handleOnPrevious = () => { + if (activeTimelineItem > 0) { + const newTimeLineItem = activeTimelineItem - 1; + + handleTimelineUpdate(newTimeLineItem); + setActiveTimelineItem(newTimeLineItem); + } + }; + + const handleFirst = () => { + setActiveTimelineItem(0); + handleTimelineUpdate(0); + }; + + const handleLast = () => { + if (timeLineItems.length) { + const idx = timeLineItems.length - 1; + setActiveTimelineItem(idx); + handleTimelineUpdate(idx); + } + }; + + const handleOutlineSelection = useCallback( + (index: number) => { + if (index >= 0) { + setActiveTimelineItem(index); + handleTimelineUpdate(index); + } + }, + [timeLineItems.length], + ); + + const onPaused = useCallback(() => { + setSlideshowActive(false); + }, []); + + let iconChildren = toReactArray(children).filter( + (item) => (item as any).props.className === 'chrono-icons', + ); + + if (iconChildren.length) { + iconChildren = (iconChildren[0] as any).props.children; + } + + return ( + <GlobalContextProvider {...props}> + <Timeline + activeTimelineItem={activeTimelineItem} + contentDetailsChildren={toReactArray(children).filter( + (item) => (item as any).props.className !== 'chrono-icons', + )} + iconChildren={iconChildren} + items={timeLineItems} + onFirst={handleFirst} + onLast={handleLast} + onNext={handleOnNext} + onPrevious={handleOnPrevious} + onRestartSlideshow={restartSlideShow} + onTimelineUpdated={handleTimelineUpdate} + slideShow={slideShow} + slideShowEnabled={slideShow} + slideShowRunning={slideShowActive} + onScrollEnd={onScrollEnd} + onItemSelected={onItemSelected} + onOutlineSelection={handleOutlineSelection} + mode={mode} + enableOutline={enableOutline} + hideControls={hideControls} + onPaused={onPaused} + /> + </GlobalContextProvider> + ); +}; + +export default Chrono; + |
+ 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 | +
memoized-model.ts | +
+
+ |
+ 0% | +0/53 | +0% | +0/1 | +0% | +0/1 | +0% | +0/53 | +
+ 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 +47 +48 +49 +50 +51 +52 +53 +54 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { Theme } from '@models/Theme'; +import React, { ForwardRefExoticComponent, ReactNode } from 'react'; +import { TextOrContentModel } from '../timeline-card-content/text-or-content'; + +type common = { + classString?: string; + color?: string; + dir?: string; + fontSize?: string; + padding?: boolean; + theme?: Theme; +}; + +export interface Title extends common { + active?: boolean; + padding?: boolean; + title?: string; + url?: string; +} + +export interface Content extends common { + content?: string | ReactNode; +} + +export type ExpandButtonModel = { + expanded?: boolean; + onExpand?: (ev: React.PointerEvent | React.KeyboardEvent) => void; + textOverlay?: boolean; +} & Pick<common, 'theme'>; + +export type ShowHideTextButtonModel = { + onToggle: (ev: React.PointerEvent | React.KeyboardEvent) => void; + show?: boolean; + textOverlay?: boolean; +} & Pick<common, 'theme'>; + +export type DetailsTextMemoModel = { + expand?: boolean; + height?: number; + onRender?: (height?: number) => void; + show?: boolean; + text: ForwardRefExoticComponent<TextOrContentModel>; + textOverlay?: boolean; + theme?: Theme; +}; + +export type TextContentMemoModel = Title & + Content & + ExpandButtonModel & + ShowHideTextButtonModel & + DetailsTextMemoModel; + +export type CardMediaHeaderMemoModel = Title & Content; + |
+ 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. +
+ +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 | + + + + + + + + + + + + + + + + + + + + + + + + + | import { Theme } from '@models/Theme'; +import { TimelineContentModel } from '@models/TimelineContentModel'; +import { RefObject } from 'react'; + +export type ContentHeaderProps = Pick< + TimelineContentModel, + 'theme' | 'url' | 'title' | 'media' | 'content' +>; + +export type ContentFooterProps = { + canShow: boolean; + isNested?: boolean; + isResuming?: boolean; + onExpand: () => void; + paused: boolean; + progressRef: RefObject<HTMLDivElement>; + remainInterval: number; + showMore: boolean; + showProgressBar?: boolean; + showReadMore?: boolean | ''; + startWidth: number; + textContentIsLarge: boolean; + theme?: Theme; + triangleDir?: string; +}; + |
+ 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 | +
header-footer.model.ts | +
+
+ |
+ 0% | +0/25 | +0% | +0/1 | +0% | +0/1 | +0% | +0/25 | +
text-or-content.tsx | +
+
+ |
+ 65.97% | +64/97 | +33.33% | +4/12 | +100% | +1/1 | +65.97% | +64/97 | +
timeline-card-content.styles.ts | +
+
+ |
+ 85.57% | +350/409 | +69.69% | +46/66 | +96.29% | +26/27 | +85.57% | +350/409 | +
timeline-card-content.tsx | +
+
+ |
+ 77.22% | +356/461 | +59.25% | +32/54 | +0% | +0/1 | +77.22% | +356/461 | +
+ 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 | 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 +8x +8x +8x +8x +8x +8x + +8x +8x +8x + + + + + + + + + + + + + + + + + + + + + + +8x +8x + + + +8x +8x +8x +8x +8x + + + + + + + +8x +8x +8x +6x +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 sanitizeHtml from 'sanitize-html'; +import { GlobalContext } from '../../GlobalContext'; +import { + TimelineContentDetails, + TimelineSubContent, +} 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 isTextArray = Array.isArray(detailedText); + + const { fontSizes, classNames, parseDetailsTextHTML } = + useContext(GlobalContext); + + if (timelineContent) { + return <div ref={ref}>{timelineContent}</div>; + } else { + let textContent = null; + if (isTextArray) { + textContent = (detailedText as string[]).map((text, index) => { + const props = parseDetailsTextHTML + ? { + dangerouslySetInnerHTML: { + __html: sanitizeHtml(text, { + parseStyleAttributes: true, + }), + }, + } + : null; + return ( + <TimelineSubContent + key={index} + fontSize={fontSizes?.cardText} + className={classNames?.cardText} + theme={theme} + {...props} + > + {parseDetailsTextHTML ? null : text} + </TimelineSubContent> + ); + }); + } else { + textContent = parseDetailsTextHTML + ? sanitizeHtml(detailedText, { + parseStyleAttributes: true, + }) + : detailedText; + } + + const textContentProps = + parseDetailsTextHTML && !isTextArray + ? { + dangerouslySetInnerHTML: { + __html: sanitizeHtml(textContent, { + parseStyleAttributes: true, + }), + }, + } + : {}; + + return textContent ? ( + <TimelineContentDetails + className={showMore ? 'active' : ''} + ref={ref} + theme={theme} + {...textContentProps} + > + {parseDetailsTextHTML && !isTextArray ? null : 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 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 | 1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +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 +8x +8x +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 { 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; + $highlight?: boolean; + $isNested?: boolean; + $maxWidth?: number; + $minHeight?: number; + $noMedia?: boolean; + $slideShow?: TimelineProps['slideShow']; + $slideShowActive?: boolean; + $slideShowType?: TimelineProps['slideShowType']; + $textOverlay?: boolean; + } & ContentT +>` + align-items: flex-start; + background: ${(p) => p.theme.cardBgColor}; + border-radius: 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.$highlight + ? css` + &:hover { + filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.3)) brightness(1.05); + + &::before { + content: ''; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: -1; + border: 2px solid ${p.theme.primary}; + border-radius: 4px; + } + } + ` + : css``} + + ${(p) => + p.$isNested + ? css` + background: ${p.theme.nestedCardBgColor}; + box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1); + ` + : css``} + + height: ${(p) => (p.$textOverlay ? '0' : '')}; + + &:focus { + outline: 1px solid ${(p) => p.theme?.primary}; + } + + ${(p) => { + if (p.$slideShowActive && p.$active) { + if (p.$slideShowType === 'slide_in') { + return css` + animation: ${slideInFromTop} 0.5s ease-in-out; + `; + } else if ( + p.$slideShowType === 'slide_from_sides' && + p.$branchDir === 'left' + ) { + return css` + animation: ${slideInFromLeft} 0.5s ease-in-out; + `; + } else if ( + p.$slideShowType === 'slide_from_sides' && + p.$branchDir === 'right' + ) { + return css` + animation: ${slideFromRight} 0.5s ease-in-out; + `; + } else { + return css` + animation: ${reveal} 0.5s ease-in-out; + `; + } + } + }} + + ${(p) => { + if (p.$slideShowActive && p.$active) { + return css` + opacity: 1; + animation-timing-function: ease-in-out; + animation-duration: 0.5s; + `; + } + + if (p.$slideShowActive && !p.$active) { + return css` + opacity: 0; + `; + } + }} +`; + +export const TimelineCardHeader = styled.header` + width: 100%; + padding: 0.5rem 0.5rem 0 0.5rem; +`; + +export const CardSubTitle = styled.h2<{ + $fontSize?: string; + $padding?: boolean; + dir?: string; + theme?: Theme; +}>` + color: ${(p) => p.theme.cardSubtitleColor}; + font-size: ${(p) => p.$fontSize}; + font-weight: 600; + margin: 0; + text-align: left; + width: 97%; + padding: ${(p) => (p.$padding ? '0.5rem 0 0.5rem 0.5rem;' : '')}; +`; + +export const CardTitle = styled.h1<{ + $fontSize: string; + $padding?: boolean; + dir?: string; + theme: Theme; +}>` + color: ${(p) => p.theme.cardTitleColor}; + font-size: ${(p) => p.$fontSize}; + font-weight: 600; + margin: 0; + text-align: left; + width: 95%; + padding: ${(p) => (p.$padding ? '0.25rem 0 0.25rem 0.5rem;' : '')} &.active { + color: ${(p) => p.theme.primary}; + } +`; + +export const CardTitleAnchor = styled.a` + color: inherit; + + &:active { + color: inherit; + } +`; + +export const TimelineContentDetails = styled.p<{ theme?: Theme }>` + font-size: 0.85rem; + font-weight: 400; + margin: 0; + width: 100%; + color: ${(p) => p.theme.cardDetailsColor}; +`; + +export const TimelineSubContent = styled.span<{ + fontSize?: string; + theme?: Theme; +}>` + margin-bottom: 0.5rem; + display: block; + font-size: ${(p) => p.fontSize}; + color: ${(p) => p.theme.cardDetailsColor}; +`; + +export const TimelineContentDetailsWrapper = styled.div<{ + $borderLess?: boolean; + $cardHeight?: number | null; + $contentHeight?: number; + $customContent?: boolean; + $gradientColor?: string | null; + $showMore?: boolean; + $textOverlay?: boolean; + $useReadMore?: boolean; + branchDir?: string; + height?: number; + theme?: Theme; +}>` + align-items: center; + display: flex; + flex-direction: column; + margin: 0 auto; + margin-top: 0.5em; + margin-bottom: 0.5em; + position: relative; + ${({ $useReadMore, $customContent, $showMore, height = 0, $textOverlay }) => + $useReadMore && !$customContent && !$showMore && !$textOverlay + ? `max-height: ${height}px;` + : ''} + ${({ + $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 +458 +459 +460 +461 +462 | 1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +20x +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 +8x +8x +20x +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 { DetailsText } from './details-text'; +import { getTextOrContent } from './text-or-content'; +import { TimelineItemContentWrapper } from './timeline-card-content.styles'; + +const TimelineCardContent: React.FunctionComponent<TimelineContentModel> = + React.memo( + ({ + active, + content, + detailedText, + id, + media, + onShowMore, + slideShowActive, + onElapsed, + theme, + title, + onClick, + customContent, + hasFocus, + flip, + branchDir, + url, + timelineContent, + items, + isNested, + nestedCardHeight, + }: TimelineContentModel) => { + const [showMore, setShowMore] = useState(false); + const detailsRef = useRef<HTMLDivElement | null>(null); + const containerRef = useRef<HTMLDivElement | null>(null); + const progressRef = useRef<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, + disableInteraction, + highlightCardsOnHover, + } = useContext(GlobalContext); + + // If the media is a video, we don't show the progress bar. + // If the media is an image, we show the progress bar if the + // showProgressOnSlideshow flag is set. + const canShowProgressBar = useMemo(() => { + return active && slideShowActive && showProgressOnSlideshow; + }, [active, slideShowActive]); + + // This function returns a boolean value that indicates whether the user + // can see more information about the item. The detailed text is only + // available if the user has expanded the row. + const canShowMore = useMemo(() => { + return !!detailedText; + }, [detailedText]); + + useEffect(() => { + const detailsEle = detailsRef.current; + + if (detailsEle) { + detailsEle.scrollTop = 0; + } + }, [showMore]); + + useEffect(() => { + if (active) { + setHasBeenActivated(true); + } + }, [active]); + + const onContainerRef = useCallback( + (node: HTMLElement) => { + if (node === null) { + return; + } + const detailsEle = detailsRef.current; + if (!detailsEle) { + return; + } + const { scrollHeight, offsetTop } = detailsEle; + containerWidth.current = node.clientWidth; + setStartWidth(containerWidth.current); + setCardActualHeight(scrollHeight); + setDetailsHeight(detailsEle.offsetHeight); + setTextContentLarge(scrollHeight + offsetTop > node.clientHeight); + }, + [detailsRef.current], + ); + + const setupTimer = useCallback((interval: number) => { + if (!slideItemDuration) { + return; + } + + setRemainInterval(interval); + + startTime.current = new Date(); + + setPaused(false); + + timerRef.current = window.setTimeout(() => { + // clear the timer and move to the next card + window.clearTimeout(timerRef.current); + setPaused(true); + setStartWidth(0); + setRemainInterval(slideItemDuration); + id && onElapsed && onElapsed(id); + }, interval); + }, []); + + useEffect(() => { + if (timerRef.current && !slideShowActive) { + window.clearTimeout(timerRef.current); + } + }, [slideShowActive]); + + // pause the slide show + const tryHandlePauseSlideshow = useCallback(() => { + if (active && slideShowActive) { + window.clearTimeout(timerRef.current); + setPaused(true); + + if (startTime.current) { + const elapsed: any = +new Date() - +startTime.current; + slideShowElapsed.current = elapsed; + } + + if (progressRef.current) { + setStartWidth(progressRef.current.clientWidth); + } + } + }, [active, slideShowActive]); + + // resumes the slide show + const tryHandleResumeSlideshow = useCallback(() => { + if (active && slideShowActive) { + if (!slideItemDuration) { + return; + } + const remainingInterval = + slideItemDuration - slideShowElapsed.current; + + setPaused(false); + + if (remainingInterval > 0) { + setupTimer(remainingInterval); + } + } + }, [active, slideShowActive, slideItemDuration]); + + useEffect(() => { + if (!slideItemDuration) { + return; + } + // setup the timer + if (active && slideShowActive) { + setStartWidth(containerWidth.current); + setupTimer(slideItemDuration); + } + + // disabled autofocus on active + if (active && hasFocus) { + containerRef.current && containerRef.current.focus(); + } + + if (!slideShowActive) { + setHasBeenActivated(false); + } + }, [active, slideShowActive]); + + useEffect(() => { + if (hasFocus && active) { + containerRef.current && containerRef.current.focus(); + } + }, [hasFocus, active]); + + useEffect(() => { + if (!paused && !isFirstRender.current) { + setIsResuming(true); + } + }, [paused, startWidth]); + + useEffect(() => { + isFirstRender.current = false; + }, []); + + // This code is used to determine whether the read more button should be shown. + // It is only shown if the useReadMore prop is true, the detailedText is non-null, + // and the customContent prop is false. + const canShowReadMore = useMemo(() => { + return useReadMore && detailedText && !customContent; + }, []); + + // decorate the comments + // This function is triggered when the media state changes. If the slideshow is + // active, and the media state changes to paused, this function will call + // tryHandlePauseSlideshow(), which will pause the slideshow. + const handleMediaState = useCallback( + (state: MediaState) => { + if (!slideShowActive) { + return; + } + if (state.playing) { + tryHandlePauseSlideshow(); + } else if (state.paused) { + if (paused && id && onElapsed) { + onElapsed(id); + } + } + }, + [paused, slideShowActive], + ); + + const contentClass = useMemo( + () => + cls( + active ? 'timeline-card-content active' : 'timeline-card-content ', + classNames?.card, + ), + [active], + ); + + const contentDetailsClass = useMemo( + () => + cls( + !showMore && !customContent && useReadMore + ? 'show-less card-description' + : 'card-description', + classNames?.cardText, + ), + [showMore, customContent], + ); + + /** + * Calculate the minimum height of the card. If the card has a text overlay and + * media, the minimum height is equal to the card height. If the card is not + * nested, the minimum height is equal to the card height. If the card is nested, + * the minimum height is equal to the nested card height. + */ + const cardMinHeight = useMemo(() => { + if (textOverlay && media) { + return cardHeight; + } else if (!isNested) { + return cardHeight; + } else { + return nestedCardHeight; + } + }, []); + + const handleExpandDetails = useCallback(() => { + if ((active && paused) || !slideShowActive) { + setShowMore(!showMore); + onShowMore(); + } + }, [active, paused, slideShowActive, showMore]); + + const triangleDir = useMemo(() => { + if (flip) { + if (branchDir === 'right') { + return 'left'; + } else { + return 'right'; + } + } + return branchDir; + }, [branchDir, flip]); + + // Get the background color for the gradient, which is either the + // cardDetailsBackGround or nestedCardDetailsBackGround theme variable, + // based on whether the card is nested or not. If we are showing more + // content, the background color should be null, so that there is no + // gradient. + const gradientColor = useMemo(() => { + const bgToUse = !isNested + ? theme?.cardBgColor + : theme?.nestedCardDetailsBackGround; + return !showMore && textContentLarge + ? hexToRGBA(bgToUse || '#ffffff', 0.8) + : null; + }, [textContentLarge, showMore, theme?.cardDetailsBackGround, isNested]); + + // This code checks whether the textOverlay and items props are truthy. If so, then it returns false. Otherwise, it returns true. + const canShowDetailsText = useMemo(() => { + return !textOverlay && !items?.length; + }, [items?.length]); + + const TextOrContent = useMemo(() => { + return getTextOrContent({ + detailedText, + showMore, + theme, + timelineContent, + }); + }, [showMore, timelineContent, theme, detailedText]); + + const handlers = useMemo(() => { + if (!isNested && !disableInteraction) { + console.log('reddit'); + return { + onPointerDown: (ev: React.PointerEvent) => { + ev.stopPropagation(); + if ( + !slideShowActive && + onClick && + id && + !disableAutoScrollOnClick + ) { + onClick(id); + } + }, + onPointerEnter: tryHandlePauseSlideshow, + onPointerLeave: tryHandleResumeSlideshow, + }; + } + // }, [tryHandlePauseSlideshow, tryHandleResumeSlideshow]); + }, []); + + return ( + <TimelineItemContentWrapper + className={contentClass} + $minHeight={cardMinHeight} + $maxWidth={cardWidth} + mode={mode} + $noMedia={!media} + {...handlers} + ref={onContainerRef} + tabIndex={!isNested ? 0 : -1} + theme={theme} + $borderLessCards={borderLessCards} + $textOverlay={textOverlay} + $active={hasBeenActivated} + $slideShowType={slideShowType} + $slideShowActive={slideShowActive} + $branchDir={branchDir} + $isNested={isNested} + $highlight={highlightCardsOnHover} + > + {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 +9x +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.34% | +220/226 | +31.42% | +11/35 | +100% | +1/1 | +97.34% | +220/226 | +
+ 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 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 | 1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +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 +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, + disableInteraction, + } = useContext(GlobalContext); + + const rotate = useMemo(() => mode !== 'HORIZONTAL', [mode]); + + const flippedHorizontally = useMemo( + () => flipLayout && mode === 'HORIZONTAL', + [], + ); + + const canDisableLeft = useMemo( + () => disableLeft || slideShowRunning, + [disableLeft, slideShowRunning], + ); + + const canDisableRight = useMemo( + () => disableRight || slideShowRunning, + [disableRight, slideShowRunning], + ); + + const handlePlayOrPause = useCallback(() => { + if (slideShowRunning) { + onPaused?.(); + } else { + onReplay?.(); + } + }, [slideShowRunning]); + + const previousTitle = useMemo( + () => (flipLayout ? buttonTexts?.next : buttonTexts?.previous), + [flipLayout], + ); + + const nextTitle = useMemo( + () => (flipLayout ? buttonTexts?.previous : buttonTexts?.next), + [flipLayout], + ); + + const playOrPauseTile = useMemo( + () => (slideShowRunning ? buttonTexts?.stop : buttonTexts?.play), + [slideShowRunning], + ); + + const jumpToLastTitle = useMemo( + () => (flipLayout ? buttonTexts?.first : buttonTexts?.last), + [flipLayout], + ); + + const jumpToFirstTitle = useMemo( + () => (flipLayout ? buttonTexts?.last : buttonTexts?.first), + [flipLayout], + ); + + return ( + <TimelineControlContainer> + <TimelineNavWrapper + className={cls('timeline-controls', classNames?.controls)} + > + {/* jump to first */} + {disableInteraction ? null : ( + <> + <TimelineNavItem $disable={canDisableLeft}> + <TimelineNavButton + mode={mode} + theme={theme} + onClick={flippedHorizontally ? onLast : onFirst} + title={jumpToFirstTitle} + aria-label={jumpToFirstTitle} + aria-disabled={disableLeft} + aria-controls="timeline-main-wrapper" + tabIndex={!disableLeft ? 0 : -1} + rotate={rotate ? 'TRUE' : 'FALSE'} + > + <ChevronsLeftIcon /> + </TimelineNavButton> + </TimelineNavItem> + + {/* previous */} + <TimelineNavItem $disable={canDisableLeft}> + <TimelineNavButton + mode={mode} + theme={theme} + onClick={flippedHorizontally ? onNext : onPrevious} + title={previousTitle} + aria-label={previousTitle} + aria-disabled={disableLeft} + aria-controls="timeline-main-wrapper" + tabIndex={!disableLeft ? 0 : -1} + rotate={rotate ? 'TRUE' : 'FALSE'} + > + <ChevronLeft /> + </TimelineNavButton> + </TimelineNavItem> + + {/* next */} + <TimelineNavItem $disable={canDisableRight}> + <TimelineNavButton + mode={mode} + theme={theme} + onClick={flippedHorizontally ? onPrevious : onNext} + title={nextTitle} + aria-label={nextTitle} + aria-disabled={disableRight} + aria-controls="timeline-main-wrapper" + rotate={rotate ? 'TRUE' : 'FALSE'} + tabIndex={!disableRight ? 0 : -1} + > + <ChevronRightIcon /> + </TimelineNavButton> + </TimelineNavItem> + + {/* jump to last */} + <TimelineNavItem $disable={canDisableRight}> + <TimelineNavButton + mode={mode} + theme={theme} + onClick={flippedHorizontally ? onFirst : onLast} + title={jumpToLastTitle} + aria-label={jumpToLastTitle} + aria-disabled={disableRight} + aria-controls="timeline-main-wrapper" + tabIndex={!disableRight ? 0 : -1} + rotate={rotate ? 'TRUE' : 'FALSE'} + > + <ChevronsRightIcon /> + </TimelineNavButton> + </TimelineNavItem> + </> + )} + + {/* slideshow button */} + <TimelineNavItem> + {slideShowEnabled && ( + <TimelineNavButton + theme={theme} + onClick={handlePlayOrPause} + title={playOrPauseTile} + tabIndex={0} + aria-controls="timeline-main-wrapper" + aria-label={playOrPauseTile} + > + {slideShowRunning ? <StopIcon /> : <ReplayIcon />} + </TimelineNavButton> + )} + </TimelineNavItem> + + {/* dark toggle button */} + {enableDarkToggle ? ( + <TimelineNavItem $disable={slideShowRunning}> + <TimelineNavButton + theme={theme} + onClick={onToggleDarkMode} + title={isDark ? buttonTexts?.light : buttonTexts?.dark} + tabIndex={0} + aria-controls="timeline-main-wrapper" + aria-label={isDark ? buttonTexts?.light : buttonTexts?.dark} + > + {isDark ? <SunIcon /> : <MoonIcon />} + </TimelineNavButton> + </TimelineNavItem> + ) : null} + </TimelineNavWrapper> + </TimelineControlContainer> + ); +}; + +TimelineControl.displayName = 'Timeline Control'; + +export default TimelineControl; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
timeline-card-title.styles.ts | +
+
+ |
+ 100% | +24/24 | +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.18% | +227/236 | +30.76% | +4/13 | +100% | +1/1 | +96.18% | +227/236 | +
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 +237 | 1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +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 +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, + disableInteraction, + } = useContext(GlobalContext); + + // handler for onActive + const handleOnActive = useCallback( + (offset: number) => { + if (contentRef.current) { + const { offsetTop, clientHeight } = contentRef.current; + onActive(offsetTop + offset, offsetTop, clientHeight); + } + }, + [onActive], + ); + + // handler for read more + const handleShowMore = useCallback(() => { + setTimeout(() => { + handleOnActive(0); + }, 100); + }, [handleOnActive]); + + // timeline title + const Title = useMemo(() => { + return ( + <TimelineTitleWrapper + className={className} + $alternateCards={alternateCards} + mode={mode} + $hide={!title} + $flip={flipLayout} + > + <TimelineItemTitle + title={title} + active={active && !disableInteraction} + theme={theme} + align={flipLayout ? 'left' : 'right'} + classString={classNames?.title} + /> + </TimelineTitleWrapper> + ); + }, [ + active, + title, + className, + alternateCards, + mode, + flipLayout, + theme, + classNames, + ]); + + const verticalItemClass = useMemo( + () => + cls({ [className]: true }, 'vertical-item-row', visible ? 'visible' : ''), + [className, visible], + ); + + const contentClass = cls('card-content-wrapper', visible ? 'visible' : '', { + [className]: true, + }); + + // timeline circle + const TimelinePointMemo = useMemo( + () => ( + <TimelinePoint + active={active} + alternateCards={alternateCards} + className={className} + id={id} + mode={mode} + onActive={handleOnActive} + onClick={onClick} + slideShowRunning={slideShowRunning} + iconChild={iconChild} + timelinePointDimension={timelinePointDimension} + lineWidth={lineWidth} + disableClickOnCircle={disableClickOnCircle} + cardLess={cardLess} + /> + ), + [ + slideShowRunning, + active, + alternateCards, + className, + id, + mode, + handleOnActive, + onClick, + iconChild, + timelinePointDimension, + lineWidth, + disableClickOnCircle, + cardLess, + ], + ); + + return ( + <VerticalItemWrapper + $alternateCards={alternateCards} + $cardHeight={isNested ? nestedCardHeight : cardHeight} + className={verticalItemClass} + data-testid="vertical-item-row" + key={id} + ref={contentRef} + $cardLess={cardLess} + 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 | +
+
+ |
+ 6.98% | +34/487 | +100% | +0/0 | +0% | +0/1 | +6.98% | +34/487 | +
+ 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 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 | 1x +1x +1x +1x +1x +1x +1x +1x +1x +1x +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(() => { + if (hasFocus) { + activeItemIndex.current = Math.min( + activeItemIndex.current + 1, + items.length - 1, + ); + onNext?.(); + } + }, [hasFocus, onNext]); + + const handlePrevious = useCallback(() => { + if (hasFocus) { + activeItemIndex.current = Math.max(activeItemIndex.current - 1, 0); + onPrevious?.(); + } + }, [hasFocus, onPrevious]); + + const handleFirst = useCallback(() => { + if (hasFocus) { + activeItemIndex.current = 0; + onFirst?.(); + } + }, [hasFocus, onFirst]); + + const handleLast = useCallback(() => { + if (hasFocus) { + activeItemIndex.current = items.length - 1; + onLast?.(); + } + }, [hasFocus, onLast]); + + // handler for keyboard navigation + const handleKeySelection = useCallback( + (event: React.KeyboardEvent<HTMLDivElement>) => { + const { key } = event; + + if (mode === 'HORIZONTAL' && key === 'ArrowRight') { + flipLayout ? handlePrevious() : handleNext(); + } else if (mode === 'HORIZONTAL' && key === 'ArrowLeft') { + flipLayout ? handleNext() : handlePrevious(); + } else if ( + (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && + key === 'ArrowDown' + ) { + handleNext(); + } else if ( + (mode === 'VERTICAL' || mode === 'VERTICAL_ALTERNATING') && + key === 'ArrowUp' + ) { + handlePrevious(); + } else if (key === 'Home') { + handleFirst(); + } else if (key === 'End') { + handleLast(); + } + }, + [handleNext, handlePrevious, handleLast], + ); + + const handleTimelineItemClick = (itemId?: string, isSlideShow?: boolean) => { + if (itemId) { + for (let idx = 0; idx < items.length; idx++) { + if (items[idx].id === itemId) { + activeItemIndex.current = idx; + if (isSlideShow && idx < items.length - 1) { + onTimelineUpdated?.(idx + 1); + } else { + onTimelineUpdated?.(idx); + } + break; + } + } + + // 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. +
+ +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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import styled from 'styled-components'; + +const ModeWrapper = styled.div` + width: 90%; + position: relative; + border-radius: 10px; + background: #fff; + display: flex; + flex-direction: column; +`; + +export const Horizontal = styled(ModeWrapper)``; + +export const Vertical = styled(ModeWrapper)``; + +export const Wrapper = styled.div` + justify-items: center; + margin: 0 auto; + width: 95vw; + background: #fff; + display: grid; + grid-template-columns: 30% 70%; +`; + +export const ComponentContainer = styled.div<{ type?: string }>` + border-radius: 4px; + margin: 0 auto; + margin-bottom: 1rem; + padding: 1rem 0; + display: flex; + align-items: flex-start; + justify-content: flex-start; + flex-direction: column; + + ${(p) => { + switch (p.type) { + case 'desktop': + return `width: 80%;`; + case 'big-screen': + return `width: 80%;`; + case 'tablet': + return `width: 100%;`; + default: + break; + } + }} +`; + +export const ComponentContainerTree = styled.div<{ type?: string }>` + border-radius: 4px; + height: 300px; + margin: 0 auto; + + ${(p) => { + switch (p.type) { + case 'desktop': + return `height: 600px; width: 75%;`; + case 'big-screen': + return `height: 90vh; width: 100%;`; + case 'tablet': + return `height: 650px; width: 100%;`; + case 'mobile': + return `height: 650px; width: 100%;`; + default: + break; + } + }} +`; + +export const Header = styled.header` + margin: 0 auto; + display: flex; + width: 100%; + align-items: center; + justify-content: center; + margin-top: 2rem; + margin-bottom: 2rem; + flex-wrap: wrap; +`; + +export const LogoImage = styled.img``; + +export const Github = styled.img` + margin-left: 1rem; +`; + +export const Footer = styled.footer` + display: flex; + align-items: center; + justify-content: center; + height: 8rem; + flex-wrap: wrap; +`; + +export const URL = styled.a` + margin: 0 1rem; + text-decoration: none; +`; + +export const DescriptionContent = styled.p` + font-family: 'Open Sans', monospace; + font-weight: 400; + width: 95%; + margin: 0 auto; + margin: 2rem 0 2rem 2rem; + font-size: 0.85rem; +`; + +export const DescriptionHeader = styled.h3` + font-family: 'Open Sans', monospace; + font-weight: 600; + width: 95%; + margin: 0 auto; + border-bottom: 1px solid #ccc; + font-size: 1rem; + display: flex; + align-items: center; + padding-bottom: 0.75rem; +`; + +export const Description = styled.div``; + +export const FeatureSetHeader = styled.header` + width: 95%; + margin: 0 auto; + font-size: 1rem; + font-weight: 600; + border-bottom: 1px solid #ccc; + padding-bottom: 0.75rem; +`; + +export const FeatureSet = styled.ul` + list-style: none; + padding: 0; + margin: 0 auto; + width: 95%; + margin-top: 1.5rem; +`; + +export const Feature = styled.li` + font-family: 'Open Sans', monospace; + margin-bottom: 0.5rem; + font-size: 0.85rem; + font-weight: 500; + + .icon { + font-size: 1rem; + margin-right: 0.25rem; + } +`; + +export const GithubLogo = styled.div` + display: flex; + align-items: center; + justify-content: center; +`; + +export const SandBox = styled.div` + display: flex; + align-items: center; + justify-content: flex-start; + margin-left: auto; +`; + +export const ComponentLinks = styled.ul` + list-style: none; + display: flex; + justify-content: space-between; + width: 100%; + flex-direction: column; + align-items: center; + margin-top: 5rem; + margin: 0; + padding: 0; + + li { + margin: 1rem 0; + } +`; + +export const AppArea = styled.div` + padding: 1rem; + width: 100%; +`; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { Theme } from '@models/Theme'; +import { TimelineItemModel } from '@models/TimelineItemModel'; +import React, { useEffect, useState } from 'react'; +import { RouterProvider, createBrowserRouter } from 'react-router-dom'; +import './App.css'; +import data from './data'; +import mixed from './data-mixed'; +import dataNested from './data-nested'; +import DynamicLoad from './dynamic-load'; +import { + HorizontalAll, + HorizontalBasic, + HorizontalBasicCardLess, + HorizontalInitialSelectedItem, +} from './horizontal-samples'; +import { items2 as worldHistory } from './human-history'; +import { Layout } from './layout'; +import { + VerticalAlternatingNested, + VerticalBasic, + VerticalBasicCardLess, + VerticalBasicNested, + VerticalCustomContent, + VerticalCustomContent2, + VerticalNewMedia, + VerticalTree, + VerticalTreeMixed, +} from './vertical-samples'; + +const NewDemo: React.FunctionComponent = () => { + const [items, setItems] = useState<TimelineItemModel[]>([]); + const [nestedItems, setNestedItems] = useState<TimelineItemModel[]>([]); + const [historyItems, setHistoryItems] = useState<TimelineItemModel[]>([]); + const [state, setState] = useState(0); + + const [customTheme, setCustomTheme] = useState<Theme>({ + cardBgColor: '#f5f5f5', + primary: '#000', + secondary: '#FFA500', + }); + + useEffect(() => { + if (state > 0) { + setCustomTheme({ + cardBgColor: '#efefef', + primary: '#000', + secondary: '#FFA500', + }); + } else { + setCustomTheme({ + cardBgColor: '#f5f5f5', + primary: '#000', + secondary: '#FFA500', + titleColorActive: '#000', + }); + } + }, [state]); + + useEffect(() => { + const newItems = data.map( + ({ + title, + url, + cardTitle, + cardSubtitle, + cardDetailedText, + id, + // media, + timelineContent, + date, + items, + }) => ({ + title, + url, + cardTitle, + cardSubtitle, + cardDetailedText, + id, + // media, + timelineContent, + date, + items, + }), + ); + setItems(newItems); + setHistoryItems( + worldHistory.map( + ({ cardTitle, cardSubtitle, cardDetailedText, media, url, title }) => ({ + cardTitle, + cardSubtitle, + cardDetailedText, + media, + url, + title, + }), + ), + ); + setNestedItems( + dataNested.map( + ({ + cardTitle, + cardSubtitle, + cardDetailedText, + media, + url, + title, + items, + }) => ({ + cardTitle, + cardSubtitle, + cardDetailedText, + url, + title, + items, + }), + ), + ); + }, []); + + const router = createBrowserRouter([ + { + path: '/', + element: <Layout />, + children: [ + { + path: '/', + element: items.length ? ( + <VerticalBasic type={'big-screen'} items={items} /> + ) : null, + }, + { + path: '/vertical-basic', + element: items.length ? ( + <VerticalBasic type={'big-screen'} items={items} /> + ) : null, + }, + { + path: '/vertical-basic-nested', + element: items.length ? ( + <VerticalBasicNested type={'big-screen'} items={dataNested} /> + ) : null, + }, + { + path: '/vertical-alternating-mixed', + element: items.length > 0 && ( + <VerticalTreeMixed type={'big-screen'} /> + ), + }, + { + path: '/vertical-alternating-nested', + element: items.length > 0 && ( + <VerticalAlternatingNested + type={'big-screen'} + items={nestedItems} + /> + ), + }, + { + path: '/vertical-alternating-nested', + element: items.length > 0 && ( + <VerticalTreeMixed type={'big-screen'} /> + ), + }, + { + path: '/vertical-alternating', + element: ( + <> + <button + onClick={() => { + setState(1 - state); + }} + className="rounded-full" + > + change + </button> + { + <VerticalTree + type={'big-screen'} + items={state > 0 ? items : mixed} + // theme={customTheme} + > + {state} + </VerticalTree> + } + </> + ), + }, + { + path: '/horizontal', + element: items.length > 0 && ( + <HorizontalBasic items={items} type="big-screen" /> + ), + }, + { + path: '/vertical-world-history', + element: historyItems.length ? ( + <VerticalNewMedia items={historyItems} type="big-screen" /> + ) : null, + }, + { + path: '/horizontal-all', + element: items.length > 0 && ( + <HorizontalAll items={historyItems} type="big-screen" /> + ), + }, + { + path: '/horizontal-initial-select', + element: items.length > 0 && ( + <HorizontalInitialSelectedItem items={items} type="big-screen" /> + ), + }, + { + path: '/vertical-custom', + element: items.length > 0 && ( + <VerticalCustomContent type="big-screen" /> + ), + }, + { + path: '/vertical-custom-icon', + element: items.length > 0 && ( + <VerticalCustomContent2 type="big-screen" items={items} /> + ), + }, + { + path: '/dynamic-load', + element: items.length > 0 && <DynamicLoad />, + }, + { + path: '/timeline-without-cards', + element: items.length > 0 && ( + <VerticalBasicCardLess type="big-screen" items={items} /> + ), + }, + { + path: '/timeline-without-cards-horizontal', + element: items.length > 0 && ( + <HorizontalBasicCardLess type="big-screen" items={items} /> + ), + }, + { + path: '/timeline-without-cards-horizontal', + element: items.length > 0 && ( + <HorizontalBasicCardLess type="big-screen" items={items} /> + ), + }, + ], + }, + ]); + + return <RouterProvider router={router}></RouterProvider>; +}; + +export default NewDemo; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { TimelineItemModel } from '@models/TimelineItemModel'; + +const items: TimelineItemModel[] = [ + { + title: 'May 1940', + cardTitle: 'Dunkirk', + url: 'http://www.google.com', + media: { + name: 'dunkirk beach', + source: { + url: 'https://i2-prod.mirror.co.uk/incoming/article10847802.ece/ALTERNATES/s810/PAY-Dunkirk-in-colour.jpg', + // url: "/dunkirk.mp4" + }, + type: 'IMAGE', + }, + cardSubtitle: + 'Men of the British Expeditionary Force (BEF) wade out to a destroyer during the evacuation from Dunkirk.', + cardDetailedText: `On 10 May 1940, Hitler began his long-awaited offensive in the west`, + }, + { + title: '25 July 1940', + cardTitle: 'The Battle of Britain', + // items: [ + // { + // cardTitle: 'The Battle of Britian Yaar', + // url: 'http://www.google.com', + // media: { + // name: 'dunkirk beach', + // source: { + // url: 'https://i2-prod.mirror.co.uk/incoming/article10847802.ece/ALTERNATES/s810/PAY-Dunkirk-in-colour.jpg', + // // url: "/dunkirk.mp4" + // }, + // type: 'IMAGE', + // }, + // cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + // }, + // { + // cardTitle: 'The Battle of Britian Yaar', + // url: 'http://www.google.com', + // cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + // }, + // { + // cardTitle: 'The Battle of Britian Yaar', + // url: 'http://www.google.com', + // cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + // }, + // ], + cardSubtitle: `RAF Spitfire pilots scramble for their planes`, + cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority. The first phase of the battle began on 10 July with Luftwaffe attacks on shipping in the Channel. + The following month, RAF Fighter Command airfields and aircraft factories came under attack. Under the dynamic direction of Lord Beaverbrook, production of Spitfire and Hurricane fighters increased, and despite its losses in pilots and planes, the RAF was never as seriously weakened as the Germans supposed.`, + }, + { + title: 'June 1941', + cardTitle: 'Operation Barbarossa', + media: { + source: { + url: 'https://www.youtube.com/embed/gPMgYC0sXos', + }, + type: 'VIDEO', + }, + cardSubtitle: `A column of Red Army prisoners taken during the first days of the German invasion`, + cardDetailedText: `Since the 1920s, Hitler had seen Russia, with its immense natural resources, as the principal target for conquest and expansion. It would provide, he believed, the necessary ‘Lebensraum’, or living space, for the German people. And by conquering Russia, Hitler would also destroy the “Jewish pestilential creed of Bolshevism”. His non-aggression pact with Stalin in August 1939 he regarded as a mere temporary expedient. + Barely a month after the fall of France, and while the Battle of Britain was being fought, Hitler started planning for the Blitzkrieg campaign against Russia, which began on 22 June 1941. Despite repeated warnings, Stalin was taken by surprise, and for the first few months the Germans achieved spectacular victories, capturing huge swathes of land and hundreds of thousands of prisoners. But they failed to take Moscow or Leningrad before winter set in. + On 5/6 December, the Red Army launched a counter-offensive which removed the immediate threat to the Soviet capital. It also brought the German high command to the brink of a catastrophic military crisis. Hitler stepped in and took personal command. His intervention was decisive and he later boasted, “That we overcame this winter and are today in a position again to proceed victoriously… is solely attributable to the bravery of the soldiers at the front and my firm will to hold out…”`, + }, + { + title: '7 December 1941', + cardTitle: 'Pearl Harbor', + cardSubtitle: `The destroyer USS Shaw explodes in dry dock after being hit by Japanese aircraft`, + media: { + source: { + // url: "https://pearlharborwarbirds.com/wp-content/uploads/2016/09/Awesome-Color-Photos-of-the-Attack-on-Pearl-Harbor-1.jpg", + url: '/pearl-harbor.mp4', + }, + type: 'VIDEO', + name: 'Pearl Harbor', + }, + cardDetailedText: `After Japan’s occupation of French Indo-China in July 1941, US President Franklin D Roosevelt, followed by Britain and the Netherlands, ordered the freezing of Japanese assets. + Many Japanese now believed that there was no alternative between economic ruin and going to war with the United States and the European colonial powers. In October 1941, a hardline government under General Hideki Tojo came to power, and preparations were made to deliver a devastating blow against the Americans.`, + }, + { + title: '15 February 1942', + cardTitle: 'The fall of Singapore', + media: { + type: 'IMAGE', + source: { + url: 'https://insidestory.org.au/wp-content/uploads/surrender.jpg', + }, + name: 'Fall of singapore', + }, + cardSubtitle: `Lieutenant General Arthur Percival and staff on their way to the Singapore Ford factory to negotiate the island’s surrender with General Yamashita`, + cardDetailedText: `The Japanese began their invasion of Malaya on 8 December 1941, and very soon the British and empire defenders were in full retreat. + Told previously that the Japanese were no match for European troops, morale among the defending forces slumped as General Tomoyuki Yamashita’s forces moved swiftly southwards towards Singapore. + The sinking of the British capital ships HMS Prince of Wales and Repulse by Japanese aircraft also contributed to the decline in morale, and panic began to set in among the civil population and the fighting troops. British commander Lieutenant General Arthur Percival had hoped to make a stand at Johore, but was forced to withdraw to Singapore Island. The Japanese landed there on 8/9 February, and before long the defence collapsed. To avoid further bloodshed, and with his water supply gone, Percival surrendered on 15 February. + Churchill described the surrender as, “the worst disaster… in British military history”. Over 130,000 British and empire troops surrendered to a much smaller Japanese force, which only suffered 9,824 battle casualties during the 70-day campaign. Singapore was not only a humiliating military defeat, but also a tremendous blow to the prestige of the ‘white man’ throughout Asia.`, + }, + { + title: '4 June 1942', + cardTitle: 'Midway', + cardSubtitle: `The American aircraft carrier USS Yorktown under Japanese attack during the battle of Midway`, + cardDetailedText: `For six months after Pearl Harbor, just as Admiral Yamamoto predicted, Japanese forces carried all before them, capturing Hong Kong, Malaya, the Philippines and the Dutch East Indies. In May 1942, in an attempt to consolidate their grip on their new conquests, the Japanese sought to eliminate the United States as a strategic Pacific power. + This would be done by luring into a trap the US navy carriers that had escaped Pearl Harbor, while at the same time the Japanese would occupy the Midway atoll in preparation for further attacks. The loss of the carriers would, the Japanese hoped, force the Americans to the negotiating table. In the event, it was the Americans who inflicted a crushing defeat on the Japanese. Their codebreakers were able to determine the location and date of the Japanese attack. This enabled US admiral Chester Nimitz to organise a trap of his own. + During the ensuing battle the Japanese suffered the loss of four carriers, one heavy cruiser and 248 aircraft, while American losses totalled one carrier, one destroyer and 98 planes. By their victory at Midway, the turning point of the Pacific war, the Americans were able to seize the strategic initiative from the Japanese, who had suffered irreplaceable losses. Admiral Nimitz described the battle’s success as “Essentially a victory of intelligence”, while President Roosevelt called it “Our most important victory in 1942… there we stopped the Japanese offensive.”`, + }, + { + title: '25 October 1942', + cardTitle: 'Alamein', + cardSubtitle: `German prisoners of war wait for transport after their capture at Alamein`, + media: { + type: 'IMAGE', + source: { + url: 'https://i.dailymail.co.uk/i/newpix/2018/03/08/11/49FEAE2A00000578-5477117-image-a-79_1520509429888.jpg', + }, + }, + cardDetailedText: `The North African campaign began in September1940, and for the next two years the fighting was marked by a succession of Allied and Axis advances and retreats. In the summer of 1942, the Axis forces under ‘Desert Fox’ field marshal, Erwin Rommel, looked poised to take Cairo and advance on the Suez Canal. + The British Middle East commander General Claude Auchinleck took personal command of the defending Eighth Army and halted the retreat at the strong defensive line at El Alamein. But Churchill, dissatisfied with Auchinleck, replaced him in August with General Harold Alexander, while Lieutenant -General Bernard Montgomery took over command of the Eighth Army. + Montgomery immediately began to build up an enormous superiority in men and equipment, finally launching his offensive at Alamein on 23 October 1942. By the beginning of November, the Axis forces were in full retreat, although final victory in North Africa was not achieved until May 1943. + Although Montgomery has been criticised for being too cautious in exploiting his success at Alamein, it made him a household name and he became Britain’s most popular general of the war. Churchill hailed Alamein as a “Glorious and decisive victory… the bright gleam has caught the helmets of our soldiers, and warmed and cheered all our hearts”.`, + }, + { + title: 'February 1943', + cardTitle: 'Stalingrad', + cardSubtitle: `Red Army soldiers hoist the Soviet flag over a recaptured Stalingrad factory following the German surrender`, + cardDetailedText: `Throughout September and October, under General Vassili Chuikov, the city’s defenders contested every yard of ground of the devastated city. + The Red Army’s stubborn defence allowed General Georgi Zhukov time to prepare a counterattack that was launched on 19 November 1942, and which soon trapped the Sixth Army commanded by General Friederich Paulus. + Hitler, wrongly assured by Göring that the Luftwaffe could supply Stalingrad by air, ordered Paulus to hold out. He also ordered Field Marshal Erich Manstein to break through and relieve the beleaguered Sixth Army. Manstein was unsuccessful, and on 31 January 1943 Paulus capitulated. Of the 91,000 German troops who went into captivity, less than 6,000 returned home after the war. Stalingrad was one of Germany’s greatest defeats, and it effectively marked the end of Hitler’s dreams of an empire in the east. + `, + }, + { + title: '6 June 1944', + cardTitle: 'D-Day, Operation Overlord', + cardSubtitle: `British commandos of the First Special Service Brigade land on Sword Beach`, + media: { + type: 'VIDEO', + source: { + // url: "https://www.britishpoles.uk/wp-content/uploads/2019/06/D-Day-landings-1.jpg" + url: '/d-day.mp4', + type: 'mp4', + }, + }, + cardDetailedText: `Operation Overlord, the invasion and liberation of north-west Europe, began on D-Day, 6 June 1944. + That day, under the overall command of US General Dwight Eisenhower, British, Canadian and American troops, supported by the Allied navies and air forces, came ashore on the coast of Normandy. By the end of the day, 158,000 men, including airborne troops, had landed. Initially, except on the American Omaha beach, German resistance was unexpectedly light. But it soon stiffened and the Allied breakout from the beachhead area was painfully slow. + The fierceness of the fighting can be gauged by the fact that in Normandy British infantry battalions were suffering the same percentage casualty rates as they had on the Western Front in 1914–1918. Eventually the breakout was achieved, and on 25 August, Paris was liberated. Brussels followed on 3 September. Hopes that the war might be won in 1944 were dashed by the Allied failure at Arnhem and the unexpected German offensive in the Ardennes in December. + It was not until 4 May 1945 that the German forces in north-west Europe surrendered to Montgomery at his HQ on Lüneburg Heath.`, + }, + { + title: 'February 1945', + cardTitle: 'The Big Three', + cardSubtitle: `Churchill, Roosevelt and Stalin sit for a group photograph during the Yalta conference`, + cardDetailedText: `Between June 1940 and June 1941, Britain stood alone against Hitler. But then, after the German invasion of Russia and the Japanese attack on Pearl Harbor, she gained two powerful allies. + For the next four years Churchill did his utmost to foster ‘The Grand Alliance’ against the Nazis. He even earned the grudging admiration of Nazi propaganda chief Dr Goebbels who said, “…I can feel only respect for this man, for whom no humiliation is too base and no trouble too great when the victory of the Allies is at stake”. + Churchill conferred with both Roosevelt and Stalin to hammer out strategy and to discuss postwar arrangements. The three men congregated for the first time at Tehran in November 1943. There, and again at their last meeting at Yalta, Churchill was conscious of the fact that Britain, exhausted by her war effort, was now very much the junior partner of the two emerging superpowers.`, + }, + { + title: '13/14 February 1945', + cardTitle: 'Dresden', + cardSubtitle: `Dresden under incendiary bomb attack`, + cardDetailedText: `At Yalta, an Allied plan to bomb the hitherto untouched city of Dresden was discussed. The reason for attacking the city was due principally to its strategic importance as a communications centre in the rear of the German retreat that followed the Soviet winter offensive of January 1945. It was also believed that Dresden might be used as an alternative to Berlin as the Reich capital. + The attack was part of a plan codenamed ‘Thunderclap’, designed to convince the Germans that the war was lost. It was drawn up in January 1945, when Hitler’s Ardennes offensive, V2 rocket attacks on Britain and the deployment of snorkel-equipped U-boats clearly demonstrated that Germany was still capable of offering stubborn resistance. Strategic bombing attacks had previously failed to break Germany, although they had proved valuable in reducing its capacity to wage war. + Now, on the night of 13/14 February 1945, Dresden was attacked by 800 RAF bombers, followed by 400 bombers of the United States Army Air Force. The bombing created a firestorm that destroyed 1,600 acres of Dresden. Even today it is still uncertain as to how many died and estimates have ranged from 25,000 to 135,000. Most authorities now put the death toll at around 35,000. The scale of destruction, the enormous death toll, and its timing at such a late stage in the war, have all ensured that the bombing of Dresden still remains highly controversial.`, + }, + { + title: '8 May 1945', + cardTitle: 'VE Day', + media: { + type: 'IMAGE', + source: { + url: 'https://ic.c4assets.com/brands/ve-day-in-colour-britains-biggest-party/d708c2d9-8fec-4592-a90f-ddfe3513c914.jpg?interpolation=progressive-bicubic&output-quality=90', + }, + }, + cardSubtitle: `millions of people rejoice in the news that Germany has surrendered – the war in Europe was finally over`, + cardDetailedText: `On the afternoon of 8 May 1945, the British prime minister Winston Churchill made the radio announcement that the world had long been waiting for. + “Yesterday morning,” he declared, “at 2.41 a.m., at General Eisenhower’s headquarters, General Jodl, the representative of the German High Command, and Grand Admiral Dönitz, the designated head of the German State, signed the act of unconditional surrender of all German land, sea and air forces in Europe.” + After nearly six years, the war in Europe was finally over.`, + }, + { + title: '9 August 1945', + cardTitle: 'Nagasaki', + cardSubtitle: `Atomic bomb mushroom cloud over the Japanese city of Nagasaki`, + cardDetailedText: `The Second World War began at dawn on Friday 1 September 1939, when Adolf Hitler launched his invasion of Poland. + The Poles fought bravely, but they were heavily outnumbered in both men and machines, and especially in the air. Britain and France declared war on Germany on 3 September 1939, but gave no real assistance to Poland. Two weeks later, Stalin invaded eastern Poland, and on 27 September Warsaw surrendered. Organised Polish resistance ceased after another week’s fighting. Poland was divided up between Hitler and Stalin. + In Poland the Nazis unleashed a reign of terror that was eventually to claim six million victims, half of whom were Polish Jews murdered in extermination camps. The Soviet regime was no less harsh. In March and April 1940, Stalin ordered the murder of over 20,000 Polish officers and others who had been captured in September 1939. Tens of thousands of Poles were also forcibly deported to Siberia. + By May 1945, and despite his promises to Churchill and Roosevelt, Stalin had installed a subservient communist regime in Poland. + Back in 1939, Poland’s then-leader Marshal Eduard Smigly-Rydz had warned, “With the Germans we risk losing our liberty, but with the Russians we lose our soul.”`, + }, +]; + +export default items; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { TimelineItemModel } from '@models/TimelineItemModel'; + +const items: TimelineItemModel[] = [ + { + title: 'May 1940', + cardTitle: 'Dunkirk', + url: 'http://www.google.com', + media: { + name: 'dunkirk beach', + source: { + url: 'https://i2-prod.mirror.co.uk/incoming/article10847802.ece/ALTERNATES/s810/PAY-Dunkirk-in-colour.jpg', + // url: "/dunkirk.mp4" + }, + type: 'IMAGE', + }, + cardSubtitle: + 'Men of the British Expeditionary Force (BEF) wade out to a destroyer during the evacuation from Dunkirk.', + cardDetailedText: `On 10 May 1940, Hitler began his long-awaited offensive in the west`, + }, + { + title: '25 July 1940', + cardTitle: 'The Battle of Britain', + items: [ + // { + // cardTitle: 'May 10, 1940', + // cardDetailedText: + // 'Germany invades France, Belgium, and the Netherlands', + // url: 'http://www.google.com', + // // media: { + // // name: 'dunkirk beach', + // // source: { + // // url: 'https://i2-prod.mirror.co.uk/incoming/article10847802.ece/ALTERNATES/s810/PAY-Dunkirk-in-colour.jpg', + // // // url: "/dunkirk.mp4" + // // }, + // // type: 'IMAGE', + // // }, + // }, + { + cardTitle: 'June 18, 1940', + url: 'http://www.google.com', + cardDetailedText: `Winston Churchill delivers his famous "Finest Hour" speech to the House of Commons, rallying the British people to prepare for the Battle of Britain. The speech emphasizes the importance of standing up to Nazi aggression and defending the country at all costs.`, + }, + { + cardTitle: 'August 8, 1940', + cardDetailedText: + 'The Germans launch "Adler Tag" (Eagle Day), a massive assault on British airfields and radar stations. The Luftwaffe hopes to achieve a decisive victory and gain control of the skies over Britain, but the RAF is able to mount a strong defense and inflict heavy losses on the Germans', + url: 'http://www.google.com', + }, + // { + // cardTitle: 'August 15, 1940', + // cardDetailedText: `The Luftwaffe mistakenly bombs London, prompting Churchill to order retaliatory attacks on Berlin. The bombing of London marks a significant shift in the German strategy, as the focus shifts from attacking airfields to targeting cities and civilian populations`, + // url: 'http://www.google.com', + // }, + // { + // cardTitle: 'August 24, 1940', + // cardDetailedText: ` The Germans launch a second wave of attacks on British airfields, known as "The Hardest Day." The attacks result in the heaviest losses of the battle for the RAF, but the Germans also suffer significant losses.`, + // url: 'http://www.google.com', + // }, + ], + cardSubtitle: `RAF Spitfire pilots scramble for their planes`, + cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority. The first phase of the battle began on 10 July with Luftwaffe attacks on shipping in the Channel. + The following month, RAF Fighter Command airfields and aircraft factories came under attack. Under the dynamic direction of Lord Beaverbrook, production of Spitfire and Hurricane fighters increased, and despite its losses in pilots and planes, the RAF was never as seriously weakened as the Germans supposed.`, + }, + { + title: 'June 1941', + cardTitle: 'Operation Barbarossa', + media: { + source: { + url: 'https://www.youtube.com/embed/gPMgYC0sXos', + }, + type: 'VIDEO', + }, + cardSubtitle: `A column of Red Army prisoners taken during the first days of the German invasion`, + cardDetailedText: `Since the 1920s, Hitler had seen Russia, with its immense natural resources, as the principal target for conquest and expansion. It would provide, he believed, the necessary ‘Lebensraum’, or living space, for the German people. And by conquering Russia, Hitler would also destroy the “Jewish pestilential creed of Bolshevism”. His non-aggression pact with Stalin in August 1939 he regarded as a mere temporary expedient. + Barely a month after the fall of France, and while the Battle of Britain was being fought, Hitler started planning for the Blitzkrieg campaign against Russia, which began on 22 June 1941. Despite repeated warnings, Stalin was taken by surprise, and for the first few months the Germans achieved spectacular victories, capturing huge swathes of land and hundreds of thousands of prisoners. But they failed to take Moscow or Leningrad before winter set in. + On 5/6 December, the Red Army launched a counter-offensive which removed the immediate threat to the Soviet capital. It also brought the German high command to the brink of a catastrophic military crisis. Hitler stepped in and took personal command. His intervention was decisive and he later boasted, “That we overcame this winter and are today in a position again to proceed victoriously… is solely attributable to the bravery of the soldiers at the front and my firm will to hold out…”`, + }, + { + title: '7 December 1941', + cardTitle: 'Pearl Harbor', + cardSubtitle: `The destroyer USS Shaw explodes in dry dock after being hit by Japanese aircraft`, + media: { + source: { + // url: "https://pearlharborwarbirds.com/wp-content/uploads/2016/09/Awesome-Color-Photos-of-the-Attack-on-Pearl-Harbor-1.jpg", + url: '/pearl-harbor.mp4', + }, + type: 'VIDEO', + name: 'Pearl Harbor', + }, + cardDetailedText: `After Japan’s occupation of French Indo-China in July 1941, US President Franklin D Roosevelt, followed by Britain and the Netherlands, ordered the freezing of Japanese assets. + Many Japanese now believed that there was no alternative between economic ruin and going to war with the United States and the European colonial powers. In October 1941, a hardline government under General Hideki Tojo came to power, and preparations were made to deliver a devastating blow against the Americans.`, + }, + { + title: '15 February 1942', + cardTitle: 'The fall of Singapore', + media: { + type: 'IMAGE', + source: { + url: 'https://insidestory.org.au/wp-content/uploads/surrender.jpg', + }, + name: 'Fall of singapore', + }, + cardSubtitle: `Lieutenant General Arthur Percival and staff on their way to the Singapore Ford factory to negotiate the island’s surrender with General Yamashita`, + cardDetailedText: `The Japanese began their invasion of Malaya on 8 December 1941, and very soon the British and empire defenders were in full retreat. + Told previously that the Japanese were no match for European troops, morale among the defending forces slumped as General Tomoyuki Yamashita’s forces moved swiftly southwards towards Singapore. + The sinking of the British capital ships HMS Prince of Wales and Repulse by Japanese aircraft also contributed to the decline in morale, and panic began to set in among the civil population and the fighting troops. British commander Lieutenant General Arthur Percival had hoped to make a stand at Johore, but was forced to withdraw to Singapore Island. The Japanese landed there on 8/9 February, and before long the defence collapsed. To avoid further bloodshed, and with his water supply gone, Percival surrendered on 15 February. + Churchill described the surrender as, “the worst disaster… in British military history”. Over 130,000 British and empire troops surrendered to a much smaller Japanese force, which only suffered 9,824 battle casualties during the 70-day campaign. Singapore was not only a humiliating military defeat, but also a tremendous blow to the prestige of the ‘white man’ throughout Asia.`, + }, + { + title: '4 June 1942', + cardTitle: 'Midway', + cardSubtitle: `The American aircraft carrier USS Yorktown under Japanese attack during the battle of Midway`, + cardDetailedText: `For six months after Pearl Harbor, just as Admiral Yamamoto predicted, Japanese forces carried all before them, capturing Hong Kong, Malaya, the Philippines and the Dutch East Indies. In May 1942, in an attempt to consolidate their grip on their new conquests, the Japanese sought to eliminate the United States as a strategic Pacific power. + This would be done by luring into a trap the US navy carriers that had escaped Pearl Harbor, while at the same time the Japanese would occupy the Midway atoll in preparation for further attacks. The loss of the carriers would, the Japanese hoped, force the Americans to the negotiating table. In the event, it was the Americans who inflicted a crushing defeat on the Japanese. Their codebreakers were able to determine the location and date of the Japanese attack. This enabled US admiral Chester Nimitz to organise a trap of his own. + During the ensuing battle the Japanese suffered the loss of four carriers, one heavy cruiser and 248 aircraft, while American losses totalled one carrier, one destroyer and 98 planes. By their victory at Midway, the turning point of the Pacific war, the Americans were able to seize the strategic initiative from the Japanese, who had suffered irreplaceable losses. Admiral Nimitz described the battle’s success as “Essentially a victory of intelligence”, while President Roosevelt called it “Our most important victory in 1942… there we stopped the Japanese offensive.”`, + }, + { + title: '25 October 1942', + cardTitle: 'Alamein', + cardSubtitle: `German prisoners of war wait for transport after their capture at Alamein`, + media: { + type: 'IMAGE', + source: { + url: 'https://i.dailymail.co.uk/i/newpix/2018/03/08/11/49FEAE2A00000578-5477117-image-a-79_1520509429888.jpg', + }, + }, + cardDetailedText: `The North African campaign began in September1940, and for the next two years the fighting was marked by a succession of Allied and Axis advances and retreats. In the summer of 1942, the Axis forces under ‘Desert Fox’ field marshal, Erwin Rommel, looked poised to take Cairo and advance on the Suez Canal. + The British Middle East commander General Claude Auchinleck took personal command of the defending Eighth Army and halted the retreat at the strong defensive line at El Alamein. But Churchill, dissatisfied with Auchinleck, replaced him in August with General Harold Alexander, while Lieutenant -General Bernard Montgomery took over command of the Eighth Army. + Montgomery immediately began to build up an enormous superiority in men and equipment, finally launching his offensive at Alamein on 23 October 1942. By the beginning of November, the Axis forces were in full retreat, although final victory in North Africa was not achieved until May 1943. + Although Montgomery has been criticised for being too cautious in exploiting his success at Alamein, it made him a household name and he became Britain’s most popular general of the war. Churchill hailed Alamein as a “Glorious and decisive victory… the bright gleam has caught the helmets of our soldiers, and warmed and cheered all our hearts”.`, + }, + { + title: 'February 1943', + cardTitle: 'Stalingrad', + cardSubtitle: `Red Army soldiers hoist the Soviet flag over a recaptured Stalingrad factory following the German surrender`, + cardDetailedText: `Throughout September and October, under General Vassili Chuikov, the city’s defenders contested every yard of ground of the devastated city. + The Red Army’s stubborn defence allowed General Georgi Zhukov time to prepare a counterattack that was launched on 19 November 1942, and which soon trapped the Sixth Army commanded by General Friederich Paulus. + Hitler, wrongly assured by Göring that the Luftwaffe could supply Stalingrad by air, ordered Paulus to hold out. He also ordered Field Marshal Erich Manstein to break through and relieve the beleaguered Sixth Army. Manstein was unsuccessful, and on 31 January 1943 Paulus capitulated. Of the 91,000 German troops who went into captivity, less than 6,000 returned home after the war. Stalingrad was one of Germany’s greatest defeats, and it effectively marked the end of Hitler’s dreams of an empire in the east. + `, + }, + { + title: '6 June 1944', + cardTitle: 'D-Day, Operation Overlord', + cardSubtitle: `British commandos of the First Special Service Brigade land on Sword Beach`, + media: { + type: 'VIDEO', + source: { + // url: "https://www.britishpoles.uk/wp-content/uploads/2019/06/D-Day-landings-1.jpg" + url: '/d-day.mp4', + type: 'mp4', + }, + }, + cardDetailedText: `Operation Overlord, the invasion and liberation of north-west Europe, began on D-Day, 6 June 1944. + That day, under the overall command of US General Dwight Eisenhower, British, Canadian and American troops, supported by the Allied navies and air forces, came ashore on the coast of Normandy. By the end of the day, 158,000 men, including airborne troops, had landed. Initially, except on the American Omaha beach, German resistance was unexpectedly light. But it soon stiffened and the Allied breakout from the beachhead area was painfully slow. + The fierceness of the fighting can be gauged by the fact that in Normandy British infantry battalions were suffering the same percentage casualty rates as they had on the Western Front in 1914–1918. Eventually the breakout was achieved, and on 25 August, Paris was liberated. Brussels followed on 3 September. Hopes that the war might be won in 1944 were dashed by the Allied failure at Arnhem and the unexpected German offensive in the Ardennes in December. + It was not until 4 May 1945 that the German forces in north-west Europe surrendered to Montgomery at his HQ on Lüneburg Heath.`, + }, + { + title: 'February 1945', + cardTitle: 'The Big Three', + cardSubtitle: `Churchill, Roosevelt and Stalin sit for a group photograph during the Yalta conference`, + cardDetailedText: `Between June 1940 and June 1941, Britain stood alone against Hitler. But then, after the German invasion of Russia and the Japanese attack on Pearl Harbor, she gained two powerful allies. + For the next four years Churchill did his utmost to foster ‘The Grand Alliance’ against the Nazis. He even earned the grudging admiration of Nazi propaganda chief Dr Goebbels who said, “…I can feel only respect for this man, for whom no humiliation is too base and no trouble too great when the victory of the Allies is at stake”. + Churchill conferred with both Roosevelt and Stalin to hammer out strategy and to discuss postwar arrangements. The three men congregated for the first time at Tehran in November 1943. There, and again at their last meeting at Yalta, Churchill was conscious of the fact that Britain, exhausted by her war effort, was now very much the junior partner of the two emerging superpowers.`, + }, + { + title: '13/14 February 1945', + cardTitle: 'Dresden', + cardSubtitle: `Dresden under incendiary bomb attack`, + cardDetailedText: `At Yalta, an Allied plan to bomb the hitherto untouched city of Dresden was discussed. The reason for attacking the city was due principally to its strategic importance as a communications centre in the rear of the German retreat that followed the Soviet winter offensive of January 1945. It was also believed that Dresden might be used as an alternative to Berlin as the Reich capital. + The attack was part of a plan codenamed ‘Thunderclap’, designed to convince the Germans that the war was lost. It was drawn up in January 1945, when Hitler’s Ardennes offensive, V2 rocket attacks on Britain and the deployment of snorkel-equipped U-boats clearly demonstrated that Germany was still capable of offering stubborn resistance. Strategic bombing attacks had previously failed to break Germany, although they had proved valuable in reducing its capacity to wage war. + Now, on the night of 13/14 February 1945, Dresden was attacked by 800 RAF bombers, followed by 400 bombers of the United States Army Air Force. The bombing created a firestorm that destroyed 1,600 acres of Dresden. Even today it is still uncertain as to how many died and estimates have ranged from 25,000 to 135,000. Most authorities now put the death toll at around 35,000. The scale of destruction, the enormous death toll, and its timing at such a late stage in the war, have all ensured that the bombing of Dresden still remains highly controversial.`, + }, + { + title: '8 May 1945', + cardTitle: 'VE Day', + media: { + type: 'IMAGE', + source: { + url: 'https://ic.c4assets.com/brands/ve-day-in-colour-britains-biggest-party/d708c2d9-8fec-4592-a90f-ddfe3513c914.jpg?interpolation=progressive-bicubic&output-quality=90', + }, + }, + cardSubtitle: `millions of people rejoice in the news that Germany has surrendered – the war in Europe was finally over`, + cardDetailedText: `On the afternoon of 8 May 1945, the British prime minister Winston Churchill made the radio announcement that the world had long been waiting for. + “Yesterday morning,” he declared, “at 2.41 a.m., at General Eisenhower’s headquarters, General Jodl, the representative of the German High Command, and Grand Admiral Dönitz, the designated head of the German State, signed the act of unconditional surrender of all German land, sea and air forces in Europe.” + After nearly six years, the war in Europe was finally over.`, + }, + { + title: '9 August 1945', + cardTitle: 'Nagasaki', + cardSubtitle: `Atomic bomb mushroom cloud over the Japanese city of Nagasaki`, + cardDetailedText: `The Second World War began at dawn on Friday 1 September 1939, when Adolf Hitler launched his invasion of Poland. + The Poles fought bravely, but they were heavily outnumbered in both men and machines, and especially in the air. Britain and France declared war on Germany on 3 September 1939, but gave no real assistance to Poland. Two weeks later, Stalin invaded eastern Poland, and on 27 September Warsaw surrendered. Organised Polish resistance ceased after another week’s fighting. Poland was divided up between Hitler and Stalin. + In Poland the Nazis unleashed a reign of terror that was eventually to claim six million victims, half of whom were Polish Jews murdered in extermination camps. The Soviet regime was no less harsh. In March and April 1940, Stalin ordered the murder of over 20,000 Polish officers and others who had been captured in September 1939. Tens of thousands of Poles were also forcibly deported to Siberia. + By May 1945, and despite his promises to Churchill and Roosevelt, Stalin had installed a subservient communist regime in Poland. + Back in 1939, Poland’s then-leader Marshal Eduard Smigly-Rydz had warned, “With the Germans we risk losing our liberty, but with the Russians we lose our soul.”`, + }, +]; + +export default items; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { TimelineItemModel } from '@models/TimelineItemModel'; +import * as React from 'react'; + +const items: TimelineItemModel[] = [ + { + title: 'May 1945', + cardTitle: 'Dunkirk', + url: 'http://www.history.com', + media: { + name: 'dunkirk beach', + source: { + url: 'https://i2-prod.mirror.co.uk/incoming/article10847802.ece/ALTERNATES/s810/PAY-Dunkirk-in-colour.jpg', + }, + type: 'IMAGE', + }, + // date: '1945-05-01', + cardSubtitle: + 'Men of the British Expeditionary Force (BEF) wade out to a destroyer during the evacuation from Dunkirk.', + // cardDetailedText: [`On 10 May 1940, Hitler began his long-awaited offensive in the west by invading neutral Holland and Belgium and attacking northern France.`, `Holland capitulated after only five days of fighting, and the Belgians surrendered on 28 May. With the success of the German ‘Blitzkrieg’, the British Expeditionary Force and French troops were in danger of being cut off and destroyed.`], + cardDetailedText: [ + `On 10 May 1940, <a href="http://www.google.com">Hitler</a> began his <strong>long-awaited</strong> offensive in the west by invading neutral Holland and Belgium and attacking northern France. + <br>`, + `<ul> + <li>Holland capitulated after only five days of fighting, and the Belgians surrendered on 28 May.</li> + <li>With the success of the German ‘Blitzkrieg’, the British Expeditionary Force and French troops were in danger of being cut off and destroyed.</li> + <li> + Germany’s armoured spearheads reached the Channel coast on 20 May, and the British began to evacuate their troops from Dunkirk. + </li> + <li> + The evacuation was codenamed ‘Operation Dynamo’ and was directed by Admiral Bertram Ramsay from his headquarters deep in the cliffs at Dover. + </li> + </ul> + `, + ], + // cardDetailedText: `Holland capitulated <a href="www.google.com">the</a> after <b>only</b> five days of fighting, and the Belgians surrendered on 28 May.With the success of the German ‘Blitzkrieg’, the British Expeditionary Force and French troops were in danger of being cut off and destroyed.`, + }, + { + title: '25 July 1941', + cardTitle: 'The Battle of Britian', + url: 'http://www.google.com', + media: { + name: 'Battle of britain', + source: { + url: 'https://cdn.britannica.com/84/142184-050-9814C416/aircraft-spotter-skies-London-1940.jpg', + }, + type: 'IMAGE', + }, + + // items: [ + // { + // cardTitle: 'The Battle of Britian Yaar', + // url: 'http://www.google.com', + // cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + // }, + // { + // cardTitle: 'The Battle of Britian Yaar', + // url: 'http://www.google.com', + // cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + // }, + // { + // cardTitle: 'The Battle of Britian Yaar', + // url: 'http://www.google.com', + // cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + // }, + // ], + date: '1941-07-25', + cardSubtitle: `RAF Spitfire pilots scramble for their planes`, + timelineContent: ( + <span> + After France’s surrender in June 1940, Churchill told the British + people, “Hitler knows that he will have to break us in this island or + lose the war”. To mount a successful invasion, the Germans had to gain + air superiority.`, `The first phase of the battle began on 10 July with + Luftwaffe attacks on shipping in the Channel. + </span> + ), + // cardSubtitle: `RAF Spitfire pilots scramble for their planes`, + cardDetailedText: [ + `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + `The first phase of the battle began on 10 July with Luftwaffe attacks on shipping in the Channel. + The following month, RAF Fighter Command airfields and aircraft factories came under attack.`, + `Under the dynamic direction of Lord Beaverbrook, production of Spitfire and Hurricane fighters increased, and despite its losses in pilots and planes, the RAF was never as seriously weakened as the Germans supposed.`, + ], + }, + { + title: 'June 1941', + cardTitle: 'Operation Barbarossa', + media: { + name: 'Operation Barbarossa', + source: { + url: 'https://cdn.britannica.com/01/150101-050-810CE9A9/soldiers-German-part-Soviet-Union-Operation-Barbarossa-1941.jpg', + }, + type: 'IMAGE', + }, + date: '1941-06-01', + cardSubtitle: `A column of Red Army prisoners taken during the first days of the German invasion`, + cardDetailedText: `Since the 1920s, Hitler had seen Russia, with its immense natural resources, as the principal target for conquest and expansion. It would provide, he believed, the necessary ‘Lebensraum’, or living space, for the German people. And by conquering Russia, Hitler would also destroy the “Jewish pestilential creed of Bolshevism”. His non-aggression pact with Stalin in August 1939 he regarded as a mere temporary expedient. + Barely a month after the fall of France, and while the Battle of Britain was being fought, Hitler started planning for the Blitzkrieg campaign against Russia, which began on 22 June 1941. Despite repeated warnings, Stalin was taken by surprise, and for the first few months the Germans achieved spectacular victories, capturing huge swathes of land and hundreds of thousands of prisoners. But they failed to take Moscow or Leningrad before winter set in. + On 5/6 December, the Red Army launched a counter-offensive which removed the immediate threat to the Soviet capital. It also brought the German high command to the brink of a catastrophic military crisis. Hitler stepped in and took personal command. His intervention was decisive and he later boasted, “That we overcame this winter and are today in a position again to proceed victoriously… is solely attributable to the bravery of the soldiers at the front and my firm will to hold out…”`, + }, + { + title: '7 December 1941', + cardTitle: 'Pearl Harbor', + cardSubtitle: `The destroyer USS Shaw explodes in dry dock after being hit by Japanese aircraft`, + media: { + source: { + url: 'https://pearlharborwarbirds.com/wp-content/uploads/2016/09/Awesome-Color-Photos-of-the-Attack-on-Pearl-Harbor-1.jpg', + }, + type: 'IMAGE', + name: 'Pearl Harbor', + }, + date: '1941-12-07', + cardDetailedText: `After Japan’s occupation of French Indo-China in July 1941, US President Franklin D Roosevelt, followed by Britain and the Netherlands, ordered the freezing of Japanese assets. + Many Japanese now believed that there was no alternative between economic ruin and going to war with the United States and the European colonial powers. In October 1941, a hardline government under General Hideki Tojo came to power, and preparations were made to deliver a devastating blow against the Americans.`, + }, + { + title: '15 February 1942', + cardTitle: 'The fall of Singapore', + media: { + type: 'IMAGE', + source: { + url: 'https://insidestory.org.au/wp-content/uploads/surrender.jpg', + }, + name: 'Fall of singapore', + }, + date: '1942-02-15', + cardSubtitle: `Lieutenant General Arthur Percival and staff on their way to the Singapore Ford factory to negotiate the island’s surrender with General Yamashita`, + cardDetailedText: `The Japanese began their invasion of Malaya on 8 December 1941, and very soon the British and empire defenders were in full retreat. + Told previously that the Japanese were no match for European troops, morale among the defending forces slumped as General Tomoyuki Yamashita’s forces moved swiftly southwards towards Singapore. + The sinking of the British capital ships HMS Prince of Wales and Repulse by Japanese aircraft also contributed to the decline in morale, and panic began to set in among the civil population and the fighting troops. British commander Lieutenant General Arthur Percival had hoped to make a stand at Johore, but was forced to withdraw to Singapore Island. The Japanese landed there on 8/9 February, and before long the defence collapsed. To avoid further bloodshed, and with his water supply gone, Percival surrendered on 15 February. + Churchill described the surrender as, “the worst disaster… in British military history”. Over 130,000 British and empire troops surrendered to a much smaller Japanese force, which only suffered 9,824 battle casualties during the 70-day campaign. Singapore was not only a humiliating military defeat, but also a tremendous blow to the prestige of the ‘white man’ throughout Asia.`, + }, + { + title: '4 June 1942', + cardTitle: 'Midway', + media: { + type: 'IMAGE', + source: { + url: 'https://hagadone.media.clients.ellingtoncms.com/img/photos/2021/09/14/0919_History_Corner2_t1170.jpg?5cc718665ab672dba93d511ab4c682bb370e5f86', + }, + }, + date: '1942-06-04', + cardSubtitle: `The American aircraft carrier USS Yorktown under Japanese attack during the battle of Midway`, + cardDetailedText: `For six months after Pearl Harbor, just as Admiral Yamamoto predicted, Japanese forces carried all before them, capturing Hong Kong, Malaya, the Philippines and the Dutch East Indies. In May 1942, in an attempt to consolidate their grip on their new conquests, the Japanese sought to eliminate the United States as a strategic Pacific power. + This would be done by luring into a trap the US navy carriers that had escaped Pearl Harbor, while at the same time the Japanese would occupy the Midway atoll in preparation for further attacks. The loss of the carriers would, the Japanese hoped, force the Americans to the negotiating table. In the event, it was the Americans who inflicted a crushing defeat on the Japanese. Their codebreakers were able to determine the location and date of the Japanese attack. This enabled US admiral Chester Nimitz to organise a trap of his own. + During the ensuing battle the Japanese suffered the loss of four carriers, one heavy cruiser and 248 aircraft, while American losses totalled one carrier, one destroyer and 98 planes. By their victory at Midway, the turning point of the Pacific war, the Americans were able to seize the strategic initiative from the Japanese, who had suffered irreplaceable losses. Admiral Nimitz described the battle’s success as “Essentially a victory of intelligence”, while President Roosevelt called it “Our most important victory in 1942… there we stopped the Japanese offensive.”`, + }, + { + title: '25 October 1942', + cardTitle: 'Alamein', + cardSubtitle: `German prisoners of war wait for transport after their capture at Alamein`, + media: { + type: 'IMAGE', + source: { + url: 'https://i.dailymail.co.uk/i/newpix/2018/03/08/11/49FEAE2A00000578-5477117-image-a-79_1520509429888.jpg', + }, + }, + date: '1942-10-25', + cardDetailedText: `The North African campaign began in September1940, and for the next two years the fighting was marked by a succession of Allied and Axis advances and retreats. In the summer of 1942, the Axis forces under ‘Desert Fox’ field marshal, Erwin Rommel, looked poised to take Cairo and advance on the Suez Canal. + The British Middle East commander General Claude Auchinleck took personal command of the defending Eighth Army and halted the retreat at the strong defensive line at El Alamein. But Churchill, dissatisfied with Auchinleck, replaced him in August with General Harold Alexander, while Lieutenant -General Bernard Montgomery took over command of the Eighth Army. + Montgomery immediately began to build up an enormous superiority in men and equipment, finally launching his offensive at Alamein on 23 October 1942. By the beginning of November, the Axis forces were in full retreat, although final victory in North Africa was not achieved until May 1943. + Although Montgomery has been criticised for being too cautious in exploiting his success at Alamein, it made him a household name and he became Britain’s most popular general of the war. Churchill hailed Alamein as a “Glorious and decisive victory… the bright gleam has caught the helmets of our soldiers, and warmed and cheered all our hearts”.`, + }, + { + title: 'February 1943', + cardTitle: 'Stalingrad', + cardSubtitle: `Red Army soldiers hoist the Soviet flag over a recaptured Stalingrad factory following the German surrender`, + media: { + type: 'IMAGE', + source: { + url: 'https://static.enlisted.net/upload/image/2022/03/art_stalingrad_1920x1080_d734ceae06e3aab39a296ad9eeacce8c.jpg', + }, + }, + date: '1943-02-01', + cardDetailedText: `Throughout September and October, under General Vassili Chuikov, the city’s defenders contested every yard of ground of the devastated city. + The Red Army’s stubborn defence allowed General Georgi Zhukov time to prepare a counterattack that was launched on 19 November 1942, and which soon trapped the Sixth Army commanded by General Friederich Paulus. + Hitler, wrongly assured by Göring that the Luftwaffe could supply Stalingrad by air, ordered Paulus to hold out. He also ordered Field Marshal Erich Manstein to break through and relieve the beleaguered Sixth Army. Manstein was unsuccessful, and on 31 January 1943 Paulus capitulated. Of the 91,000 German troops who went into captivity, less than 6,000 returned home after the war. Stalingrad was one of Germany’s greatest defeats, and it effectively marked the end of Hitler’s dreams of an empire in the east. + `, + }, + { + title: '6 June 1944', + cardTitle: 'D-Day, Operation Overlord', + cardSubtitle: `British commandos of the First Special Service Brigade land on Sword Beach`, + media: { + type: 'IMAGE', + source: { + url: 'https://www.britishpoles.uk/wp-content/uploads/2019/06/D-Day-landings-1.jpg', + }, + }, + date: '1944-06-06', + cardDetailedText: `Operation Overlord, the invasion and liberation of north-west Europe, began on D-Day, 6 June 1944. + That day, under the overall command of US General Dwight Eisenhower, British, Canadian and American troops, supported by the Allied navies and air forces, came ashore on the coast of Normandy. By the end of the day, 158,000 men, including airborne troops, had landed. Initially, except on the American Omaha beach, German resistance was unexpectedly light. But it soon stiffened and the Allied breakout from the beachhead area was painfully slow. + The fierceness of the fighting can be gauged by the fact that in Normandy British infantry battalions were suffering the same percentage casualty rates as they had on the Western Front in 1914–1918. Eventually the breakout was achieved, and on 25 August, Paris was liberated. Brussels followed on 3 September. Hopes that the war might be won in 1944 were dashed by the Allied failure at Arnhem and the unexpected German offensive in the Ardennes in December. + It was not until 4 May 1945 that the German forces in north-west Europe surrendered to Montgomery at his HQ on Lüneburg Heath.`, + }, + { + title: 'February 1945', + cardTitle: 'The Big Three', + cardSubtitle: `Churchill, Roosevelt and Stalin sit for a group photograph during the Yalta conference`, + media: { + type: 'IMAGE', + source: { + url: 'https://1.bp.blogspot.com/-GGJh_zNQ5IQ/XQvEH7JYOlI/AAAAAAAADJA/TO-j7KLr5q0zUaPhpl2KvoxihXVwBesjwCLcBGAs/s1600/Yalta_Conference_%2528Churchill%252C_Roosevelt%252C_Stalin%2529.jpg', + }, + }, + date: '1945-02-01', + cardDetailedText: `Between June 1940 and June 1941, Britain stood alone against Hitler. But then, after the German invasion of Russia and the Japanese attack on Pearl Harbor, she gained two powerful allies. + For the next four years Churchill did his utmost to foster ‘The Grand Alliance’ against the Nazis. He even earned the grudging admiration of Nazi propaganda chief Dr Goebbels who said, “…I can feel only respect for this man, for whom no humiliation is too base and no trouble too great when the victory of the Allies is at stake”. + Churchill conferred with both Roosevelt and Stalin to hammer out strategy and to discuss postwar arrangements. The three men congregated for the first time at Tehran in November 1943. There, and again at their last meeting at Yalta, Churchill was conscious of the fact that Britain, exhausted by her war effort, was now very much the junior partner of the two emerging superpowers.`, + }, + { + title: '13/14 February 1945', + cardTitle: 'Dresden', + cardSubtitle: `Dresden under incendiary bomb attack`, + media: { + type: 'IMAGE', + source: { + url: 'https://i.pinimg.com/originals/7f/13/57/7f1357ae886a0cfd317c2abbc232e637.jpg', + }, + }, + date: '1945-02-13', + cardDetailedText: `At Yalta, an Allied plan to bomb the hitherto untouched city of Dresden was discussed. The reason for attacking the city was due principally to its strategic importance as a communications centre in the rear of the German retreat that followed the Soviet winter offensive of January 1945. It was also believed that Dresden might be used as an alternative to Berlin as the Reich capital. + The attack was part of a plan codenamed ‘Thunderclap’, designed to convince the Germans that the war was lost. It was drawn up in January 1945, when Hitler’s Ardennes offensive, V2 rocket attacks on Britain and the deployment of snorkel-equipped U-boats clearly demonstrated that Germany was still capable of offering stubborn resistance. Strategic bombing attacks had previously failed to break Germany, although they had proved valuable in reducing its capacity to wage war. + Now, on the night of 13/14 February 1945, Dresden was attacked by 800 RAF bombers, followed by 400 bombers of the United States Army Air Force. The bombing created a firestorm that destroyed 1,600 acres of Dresden. Even today it is still uncertain as to how many died and estimates have ranged from 25,000 to 135,000. Most authorities now put the death toll at around 35,000. The scale of destruction, the enormous death toll, and its timing at such a late stage in the war, have all ensured that the bombing of Dresden still remains highly controversial.`, + }, + { + title: '8 May 1945', + cardTitle: 'VE Day', + media: { + type: 'IMAGE', + source: { + url: 'https://ic.c4assets.com/brands/ve-day-in-colour-britains-biggest-party/d708c2d9-8fec-4592-a90f-ddfe3513c914.jpg?interpolation=progressive-bicubic&output-quality=90', + }, + }, + date: '1945-05-08', + cardSubtitle: `millions of people rejoice in the news that Germany has surrendered – the war in Europe was finally over`, + cardDetailedText: `On the afternoon of 8 May 1945, the British prime minister Winston Churchill made the radio announcement that the world had long been waiting for. + “Yesterday morning,” he declared, “at 2.41 a.m., at General Eisenhower’s headquarters, General Jodl, the representative of the German High Command, and Grand Admiral Dönitz, the designated head of the German State, signed the act of unconditional surrender of all German land, sea and air forces in Europe.” + After nearly six years, the war in Europe was finally over.`, + }, + { + title: '9 August 1945', + cardTitle: 'Nagasaki', + cardSubtitle: `Atomic bomb mushroom cloud over the Japanese city of Nagasaki`, + media: { + type: 'IMAGE', + source: { + url: 'https://upload.wikimedia.org/wikipedia/commons/5/54/Atomic_bombing_of_Japan.jpg', + }, + }, + date: '1945-08-09', + cardDetailedText: `The Second World War began at dawn on Friday 1 September 1939, when Adolf Hitler launched his invasion of Poland. + The Poles fought bravely, but they were heavily outnumbered in both men and machines, and especially in the air. Britain and France declared war on Germany on 3 September 1939, but gave no real assistance to Poland. Two weeks later, Stalin invaded eastern Poland, and on 27 September Warsaw surrendered. Organised Polish resistance ceased after another week’s fighting. Poland was divided up between Hitler and Stalin. + In Poland the Nazis unleashed a reign of terror that was eventually to claim six million victims, half of whom were Polish Jews murdered in extermination camps. The Soviet regime was no less harsh. In March and April 1940, Stalin ordered the murder of over 20,000 Polish officers and others who had been captured in September 1939. Tens of thousands of Poles were also forcibly deported to Siberia. + By May 1945, and despite his promises to Churchill and Roosevelt, Stalin had installed a subservient communist regime in Poland. + Back in 1939, Poland’s then-leader Marshal Eduard Smigly-Rydz had warned, “With the Germans we risk losing our liberty, but with the Russians we lose our soul.”`, + }, +]; + +export default items; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | const items2 = [ + { + title: 'May 1940', + url: 'http://www.google.com', + cardTitle: 'Ohrid', + cardSubtitle: + 'Men of the British Expeditionary Force (BEF) wade out to a destroyer during the evacuation from Dunkirk.', + cardDetailedText: `On 10 May 1940, Hitler began his long-awaited offensive in the west by invading neutral Holland and Belgium and attacking northern France. Holland capitulated after only five days of fighting, and the Belgians surrendered on 28 May. With the success of the German ‘Blitzkrieg’, the British Expeditionary Force and French troops were in danger of being cut off and destroyed.`, + }, + { + title: '25 July 1940', + cardTitle: 'Berlin', + cardSubtitle: `RAF Spitfire pilots scramble for their planes`, + cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority. The first phase of the battle began on 10 July with Luftwaffe attacks on shipping in the Channel. + The following month, RAF Fighter Command airfields and aircraft factories came under attack. Under the dynamic direction of Lord Beaverbrook, production of Spitfire and Hurricane fighters increased, and despite its losses in pilots and planes, the RAF was never as seriously weakened as the Germans supposed.`, + }, + { + title: 'June 1941', + cardTitle: 'Madrid', + cardSubtitle: `A column of Red Army prisoners taken during the first days of the German invasion`, + cardDetailedText: `Since the 1920s, Hitler had seen Russia, with its immense natural resources, as the principal target for conquest and expansion. It would provide, he believed, the necessary ‘Lebensraum’, or living space, for the German people. And by conquering Russia, Hitler would also destroy the “Jewish pestilential creed of Bolshevism”. His non-aggression pact with Stalin in August 1939 he regarded as a mere temporary expedient. + Barely a month after the fall of France, and while the Battle of Britain was being fought, Hitler started planning for the Blitzkrieg campaign against Russia, which began on 22 June 1941. Despite repeated warnings, Stalin was taken by surprise, and for the first few months the Germans achieved spectacular victories, capturing huge swathes of land and hundreds of thousands of prisoners. But they failed to take Moscow or Leningrad before winter set in. + On 5/6 December, the Red Army launched a counter-offensive which removed the immediate threat to the Soviet capital. It also brought the German high command to the brink of a catastrophic military crisis. Hitler stepped in and took personal command. His intervention was decisive and he later boasted, “That we overcame this winter and are today in a position again to proceed victoriously… is solely attributable to the bravery of the soldiers at the front and my firm will to hold out…”`, + }, + { + title: '7 December 1941', + cardTitle: 'Paris', + cardSubtitle: `The destroyer USS Shaw explodes in dry dock after being hit by Japanese aircraft`, + cardDetailedText: `After Japan’s occupation of French Indo-China in July 1941, US President Franklin D Roosevelt, followed by Britain and the Netherlands, ordered the freezing of Japanese assets. + Many Japanese now believed that there was no alternative between economic ruin and going to war with the United States and the European colonial powers. In October 1941, a hardline government under General Hideki Tojo came to power, and preparations were made to deliver a devastating blow against the Americans.`, + }, + { + title: '15 February 1942', + cardTitle: 'The fall of Singapore', + cardSubtitle: `Lieutenant General Arthur Percival and staff on their way to the Singapore Ford factory to negotiate the island’s surrender with General Yamashita`, + cardDetailedText: `The Japanese began their invasion of Malaya on 8 December 1941, and very soon the British and empire defenders were in full retreat. + Told previously that the Japanese were no match for European troops, morale among the defending forces slumped as General Tomoyuki Yamashita’s forces moved swiftly southwards towards Singapore. + The sinking of the British capital ships HMS Prince of Wales and Repulse by Japanese aircraft also contributed to the decline in morale, and panic began to set in among the civil population and the fighting troops. British commander Lieutenant General Arthur Percival had hoped to make a stand at Johore, but was forced to withdraw to Singapore Island. The Japanese landed there on 8/9 February, and before long the defence collapsed. To avoid further bloodshed, and with his water supply gone, Percival surrendered on 15 February. + Churchill described the surrender as, “the worst disaster… in British military history”. Over 130,000 British and empire troops surrendered to a much smaller Japanese force, which only suffered 9,824 battle casualties during the 70-day campaign. Singapore was not only a humiliating military defeat, but also a tremendous blow to the prestige of the ‘white man’ throughout Asia.`, + }, + { + title: '4 June 1942', + cardTitle: 'Midway', + cardSubtitle: `The American aircraft carrier USS Yorktown under Japanese attack during the battle of Midway`, + cardDetailedText: `For six months after Pearl Harbor, just as Admiral Yamamoto predicted, Japanese forces carried all before them, capturing Hong Kong, Malaya, the Philippines and the Dutch East Indies. In May 1942, in an attempt to consolidate their grip on their new conquests, the Japanese sought to eliminate the United States as a strategic Pacific power. + This would be done by luring into a trap the US navy carriers that had escaped Pearl Harbor, while at the same time the Japanese would occupy the Midway atoll in preparation for further attacks. The loss of the carriers would, the Japanese hoped, force the Americans to the negotiating table. In the event, it was the Americans who inflicted a crushing defeat on the Japanese. Their codebreakers were able to determine the location and date of the Japanese attack. This enabled US admiral Chester Nimitz to organise a trap of his own. + During the ensuing battle the Japanese suffered the loss of four carriers, one heavy cruiser and 248 aircraft, while American losses totalled one carrier, one destroyer and 98 planes. By their victory at Midway, the turning point of the Pacific war, the Americans were able to seize the strategic initiative from the Japanese, who had suffered irreplaceable losses. Admiral Nimitz described the battle’s success as “Essentially a victory of intelligence”, while President Roosevelt called it “Our most important victory in 1942… there we stopped the Japanese offensive.”`, + }, + { + title: '25 October 1942', + cardTitle: 'Alamein', + cardSubtitle: `German prisoners of war wait for transport after their capture at Alamein`, + cardDetailedText: `The North African campaign began in September1940, and for the next two years the fighting was marked by a succession of Allied and Axis advances and retreats. In the summer of 1942, the Axis forces under ‘Desert Fox’ field marshal, Erwin Rommel, looked poised to take Cairo and advance on the Suez Canal. + The British Middle East commander General Claude Auchinleck took personal command of the defending Eighth Army and halted the retreat at the strong defensive line at El Alamein. But Churchill, dissatisfied with Auchinleck, replaced him in August with General Harold Alexander, while Lieutenant -General Bernard Montgomery took over command of the Eighth Army. + Montgomery immediately began to build up an enormous superiority in men and equipment, finally launching his offensive at Alamein on 23 October 1942. By the beginning of November, the Axis forces were in full retreat, although final victory in North Africa was not achieved until May 1943. + Although Montgomery has been criticised for being too cautious in exploiting his success at Alamein, it made him a household name and he became Britain’s most popular general of the war. Churchill hailed Alamein as a “Glorious and decisive victory… the bright gleam has caught the helmets of our soldiers, and warmed and cheered all our hearts”.`, + }, + { + title: 'February 1943', + cardTitle: 'Stalingrad', + cardSubtitle: `Red Army soldiers hoist the Soviet flag over a recaptured Stalingrad factory following the German surrender`, + cardDetailedText: `Throughout September and October, under General Vassili Chuikov, the city’s defenders contested every yard of ground of the devastated city. + The Red Army’s stubborn defence allowed General Georgi Zhukov time to prepare a counterattack that was launched on 19 November 1942, and which soon trapped the Sixth Army commanded by General Friederich Paulus. + Hitler, wrongly assured by Göring that the Luftwaffe could supply Stalingrad by air, ordered Paulus to hold out. He also ordered Field Marshal Erich Manstein to break through and relieve the beleaguered Sixth Army. Manstein was unsuccessful, and on 31 January 1943 Paulus capitulated. Of the 91,000 German troops who went into captivity, less than 6,000 returned home after the war. Stalingrad was one of Germany’s greatest defeats, and it effectively marked the end of Hitler’s dreams of an empire in the east. + `, + }, + { + title: '6 June 1944', + cardTitle: 'D-Day, Operation Overlord', + cardSubtitle: `British commandos of the First Special Service Brigade land on Sword Beach`, + cardDetailedText: `Operation Overlord, the invasion and liberation of north-west Europe, began on D-Day, 6 June 1944. + That day, under the overall command of US General Dwight Eisenhower, British, Canadian and American troops, supported by the Allied navies and air forces, came ashore on the coast of Normandy. By the end of the day, 158,000 men, including airborne troops, had landed. Initially, except on the American Omaha beach, German resistance was unexpectedly light. But it soon stiffened and the Allied breakout from the beachhead area was painfully slow. + The fierceness of the fighting can be gauged by the fact that in Normandy British infantry battalions were suffering the same percentage casualty rates as they had on the Western Front in 1914–1918. Eventually the breakout was achieved, and on 25 August, Paris was liberated. Brussels followed on 3 September. Hopes that the war might be won in 1944 were dashed by the Allied failure at Arnhem and the unexpected German offensive in the Ardennes in December. + It was not until 4 May 1945 that the German forces in north-west Europe surrendered to Montgomery at his HQ on Lüneburg Heath.`, + }, + { + title: 'February 1945', + cardTitle: 'The Big Three', + cardSubtitle: `Churchill, Roosevelt and Stalin sit for a group photograph during the Yalta conference`, + cardDetailedText: `Between June 1940 and June 1941, Britain stood alone against Hitler. But then, after the German invasion of Russia and the Japanese attack on Pearl Harbor, she gained two powerful allies. + For the next four years Churchill did his utmost to foster ‘The Grand Alliance’ against the Nazis. He even earned the grudging admiration of Nazi propaganda chief Dr Goebbels who said, “…I can feel only respect for this man, for whom no humiliation is too base and no trouble too great when the victory of the Allies is at stake”. + Churchill conferred with both Roosevelt and Stalin to hammer out strategy and to discuss postwar arrangements. The three men congregated for the first time at Tehran in November 1943. There, and again at their last meeting at Yalta, Churchill was conscious of the fact that Britain, exhausted by her war effort, was now very much the junior partner of the two emerging superpowers.`, + }, + { + title: '13/14 February 1945', + cardTitle: 'Dresden', + cardSubtitle: `Dresden under incendiary bomb attack`, + cardDetailedText: `At Yalta, an Allied plan to bomb the hitherto untouched city of Dresden was discussed. The reason for attacking the city was due principally to its strategic importance as a communications centre in the rear of the German retreat that followed the Soviet winter offensive of January 1945. It was also believed that Dresden might be used as an alternative to Berlin as the Reich capital. + The attack was part of a plan codenamed ‘Thunderclap’, designed to convince the Germans that the war was lost. It was drawn up in January 1945, when Hitler’s Ardennes offensive, V2 rocket attacks on Britain and the deployment of snorkel-equipped U-boats clearly demonstrated that Germany was still capable of offering stubborn resistance. Strategic bombing attacks had previously failed to break Germany, although they had proved valuable in reducing its capacity to wage war. + Now, on the night of 13/14 February 1945, Dresden was attacked by 800 RAF bombers, followed by 400 bombers of the United States Army Air Force. The bombing created a firestorm that destroyed 1,600 acres of Dresden. Even today it is still uncertain as to how many died and estimates have ranged from 25,000 to 135,000. Most authorities now put the death toll at around 35,000. The scale of destruction, the enormous death toll, and its timing at such a late stage in the war, have all ensured that the bombing of Dresden still remains highly controversial.`, + }, + { + title: '8 May 1945', + cardTitle: 'VE Day', + cardSubtitle: `millions of people rejoice in the news that Germany has surrendered – the war in Europe was finally over`, + cardDetailedText: `On the afternoon of 8 May 1945, the British prime minister Winston Churchill made the radio announcement that the world had long been waiting for. + “Yesterday morning,” he declared, “at 2.41 a.m., at General Eisenhower’s headquarters, General Jodl, the representative of the German High Command, and Grand Admiral Dönitz, the designated head of the German State, signed the act of unconditional surrender of all German land, sea and air forces in Europe.” + After nearly six years, the war in Europe was finally over.`, + }, + { + title: '9 August 1945', + cardTitle: 'Nagasaki', + cardSubtitle: `Atomic bomb mushroom cloud over the Japanese city of Nagasaki`, + cardDetailedText: `The Second World War began at dawn on Friday 1 September 1939, when Adolf Hitler launched his invasion of Poland. + The Poles fought bravely, but they were heavily outnumbered in both men and machines, and especially in the air. Britain and France declared war on Germany on 3 September 1939, but gave no real assistance to Poland. Two weeks later, Stalin invaded eastern Poland, and on 27 September Warsaw surrendered. Organised Polish resistance ceased after another week’s fighting. Poland was divided up between Hitler and Stalin. + In Poland the Nazis unleashed a reign of terror that was eventually to claim six million victims, half of whom were Polish Jews murdered in extermination camps. The Soviet regime was no less harsh. In March and April 1940, Stalin ordered the murder of over 20,000 Polish officers and others who had been captured in September 1939. Tens of thousands of Poles were also forcibly deported to Siberia. + By May 1945, and despite his promises to Churchill and Roosevelt, Stalin had installed a subservient communist regime in Poland. + Back in 1939, Poland’s then-leader Marshal Eduard Smigly-Rydz had warned, “With the Germans we risk losing our liberty, but with the Russians we lose our soul.”`, + }, +]; +export default items2; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import React, { useCallback, useEffect, useState } from 'react'; +import Chrono from '../components'; +import data from './data'; + +export default function App() { + const [pageIndex, setPageIndex] = useState(0); + const [allItems, setAllItems] = useState([null]); + const [items, setItems] = useState([null]); + const [loading, setLoading] = useState(true); + + const handleAutoLoad = useCallback(() => { + setLoading(false); + + if (items.length < 2) { + return; + } + + const newItems = [...items, ...allItems.splice(pageIndex * 2, 2)]; + + console.log('handleAutoLoad', { pageIndex, newItems }); + + setItems(newItems); + }, [items.length, pageIndex]); + + useEffect(() => { + const newAllItems = [...data]; + + // console.log('newAllItems', newAllItems); + + setAllItems(newAllItems); + setPageIndex(0); + setItems(newAllItems.splice(0, 2)); + }, []); + + useEffect(() => { + if (loading) { + handleAutoLoad(); + } + }, [loading, handleAutoLoad]); + + const handleLoadMore = useCallback(() => { + if (items.length > 13) { + return; + } + + // console.log('handleLoadMore'); + + setPageIndex(pageIndex + 1); + setLoading(true); + }, [items.length]); + + // console.log('items', items); + + return ( + <div className="App"> + <div style={{ width: '650px', height: '650px' }}> + <Chrono + items={items} + mode="VERTICAL_ALTERNATING" + scrollable={{ scrollbar: true }} + onScrollEnd={handleLoadMore} + allowDynamicUpdate={true} + /> + </div> + </div> + ); +} + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { TimelineItemModel } from '@models/TimelineItemModel'; +import React, { FunctionComponent } from 'react'; +import Chrono from '../components'; +import { + ComponentContainer, + ComponentContainerTree, + Horizontal, + Vertical, +} from './App.styles'; +import data from './data'; + +export const HorizontalBasic: React.FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ items }) => { + return ( + <Horizontal id="horizontal"> + <ComponentContainer type={'big-screen'}> + <Chrono + items={data} + mode="HORIZONTAL" + cardHeight={450} + cardWidth={550} + mediaHeight={300} + slideShow + slideItemDuration={2550} + itemWidth={300} + onItemSelected={(selected) => console.log(selected)} + timelinePointDimension={20} + timelinePointShape="square" + // cardPositionHorizontal="TOP" + buttonTexts={{ + first: 'Jump to First', + last: 'Jump to Last', + next: 'Next', + previous: 'Previous', + }} + enableDarkToggle + mediaSettings={{ + imageFit: 'cover', + }} + > + <div className="chrono-icons"> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + </div> + </Chrono> + </ComponentContainer> + </Horizontal> + ); +}; + +export const HorizontalAll: React.FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ items }) => { + const [index, setIndex] = React.useState(-1); + return ( + <Horizontal id="horizontal"> + <ComponentContainer type={'big-screen'}> + <span>{index}</span> + <Chrono + items={items} + mode="HORIZONTAL" + cardHeight={350} + cardWidth={500} + enableDarkToggle + slideShow + textOverlay + mediaHeight={250} + slideItemDuration={2550} + itemWidth={400} + focusActiveItemOnLoad + onItemSelected={(selected) => { + console.log('moye', selected.index); + setIndex(selected.index); + }} + timelinePointDimension={20} + showAllCardsHorizontal + activeItemIndex={8} + > + <div className="chrono-icons"> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + </div> + </Chrono> + </ComponentContainer> + </Horizontal> + ); +}; + +export const HorizontalInitialSelectedItem: React.FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ items }) => { + return ( + <Horizontal id="horizontal"> + <ComponentContainer type={'big-screen'}> + <Chrono + items={data} + activeItemIndex={2} + mode="HORIZONTAL" + cardHeight={150} + slideShow + slideItemDuration={2550} + itemWidth={200} + cardLess + onItemSelected={(selected) => console.log(selected)} + > + <div className="chrono-icons"> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + <img src="color-circle.svg" alt="github" /> + </div> + </Chrono> + </ComponentContainer> + </Horizontal> + ); +}; + +export const HorizontalBasicCardLess: FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ type, items }) => ( + <Vertical id="vertical"> + <ComponentContainerTree type={type}> + <Chrono + items={items} + mode="HORIZONTAL" + cardLess + theme={{ + cardBgColor: '#fff', + titleColorActive: 'red', + }} + onItemSelected={(selected) => console.log(selected.cardTitle)} + /> + </ComponentContainerTree> + </Vertical> +); + +export const HorizontalSlideshow: FunctionComponent<{ + type: string; + cardHeight?: number; + items: TimelineItemModel[]; +}> = ({ type, cardHeight }) => ( + <Horizontal id="slideshow"> + <ComponentContainer type={type}> + <Chrono + items={data} + mode="HORIZONTAL" + slideShow + slideItemDuration={1500} + cardHeight={450} + scrollable + /> + </ComponentContainer> + </Horizontal> +); + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { TimelineItemModel } from '@models/TimelineItemModel'; + +export const items2: TimelineItemModel[] = [ + { + title: '3500 BC', + url: 'https://en.wikipedia.org/wiki/Writing_system#History_of_writing_systems', + cardTitle: 'Mesopotamia', + cardSubtitle: 'First known writing systems developed in Mesopotamia', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/32/197632-050-C3FB90BC/Sumerian-cuneiform-tablet-Erech-Mesopotamia-New-York-2900-bce.jpg', + }, + }, + items: [ + { + cardTitle: 'The Battle of Britian Yaar', + url: 'http://www.google.com', + cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + }, + { + cardTitle: 'The Battle of Britian Yaar', + url: 'http://www.google.com', + cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + }, + { + cardTitle: 'The Battle of Britian Yaar', + url: 'http://www.google.com', + cardDetailedText: `After France’s surrender in June 1940, Churchill told the British people, “Hitler knows that he will have to break us in this island or lose the war”. To mount a successful invasion, the Germans had to gain air superiority.`, + }, + ], + cardDetailedText: [ + `Mesopotamia is known as the birthplace of writing. The people of ancient Mesopotamia developed the first form of writing known as cuneiform around 3500 BCE. Cuneiform was created by pressing a stylus into a soft clay tablet, making wedge-shaped marks that represented words or symbols`, + `Cuneiform was used for a wide variety of purposes, including record-keeping, trade, literature, and even religion. The earliest forms of cuneiform were pictograms, which represented objects or concepts, but it later evolved into a more complex system of phonetic signs that represented syllables or individual sounds.`, + `One of the most famous pieces of Mesopotamian writing is the Epic of Gilgamesh, an ancient poem that tells the story of a hero-king who embarks on a journey to discover the secrets of immortality. The story was written in cuneiform on a series of tablets and is considered one of the earliest works of literature.`, + `Other important Mesopotamian texts include the Code of Hammurabi, a set of laws created by the Babylonian king Hammurabi in the 18th century BCE, and the Atrahasis Epic, a creation myth that describes the origins of the world and humanity. These texts provide valuable insight into the culture, society, and beliefs of the ancient Mesopotamians.`, + ], + }, + { + title: '776 BC', + url: 'https://en.wikipedia.org/wiki/Olympic_Games#Ancient_Greece', + cardTitle: 'Greece', + media: { + type: 'IMAGE', + source: { + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Olympic_rings_without_rims.svg/1200px-Olympic_rings_without_rims.svg.png', + }, + }, + cardSubtitle: 'First Olympic Games held in ancient Greece', + cardDetailedText: `First Olympic Games held in ancient Greece: The first Olympic Games were held in 776 BC in Olympia, Greece. The games were held every four years, and were a celebration of the Greek gods. The games were held in honor of Zeus, the king of the gods, and were a way for the Greeks to show their strength and power. The games were held in honor of Zeus, the king of the gods, and were a way for the Greeks to show their strength and power.`, + }, + { + title: '753 BC', + url: 'https://en.wikipedia.org/wiki/Rome#Early_history', + cardTitle: 'Rome', + media: { + type: 'IMAGE', + source: { + url: 'https://www.toursbylocals.com/images/blog/20181231040043210.jpg', + }, + }, + cardSubtitle: 'Traditional date for the founding of Rome', + cardDetailedText: [ + 'The origins of Rome are shrouded in legend, but according to tradition, the city was founded by the twins Romulus and Remus in 753 BCE. The twins were said to be the sons of the god Mars and a vestal virgin named Rhea Silvia.', + 'As infants, Romulus and Remus were abandoned in a basket on the Tiber River and left to die. However, they were rescued by a she-wolf who nursed them and raised them as her own. As they grew up, Romulus and Remus decided to found a city in the area where they had been abandoned.', + 'However, the brothers soon fell into a quarrel over who would be the ruler of the new city. According to legend, Romulus killed Remus and became the first king of Rome.', + 'Under Romulus, Rome grew into a powerful city-state, and its people, known as the Romans, developed a sophisticated legal and political system. Rome was ruled by kings until 509 BCE, when the last king, Tarquin the Proud, was overthrown and replaced by a republic.', + 'The Roman Republic was characterized by a complex system of government that included two consuls, a senate, and popular assemblies. It also expanded its territory through conquest and warfare, eventually becoming the dominant power in the Mediterranean world.', + 'The legacy of Rome continues to influence modern Western culture, particularly in the areas of law, government, language, and architecture.', + ], + }, + { + title: '221 BC', + url: 'https://en.wikipedia.org/wiki/Qin_dynasty', + cardTitle: 'China', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/38/64938-050-FA3FE2DD/China-Qing-dynasty.jpg', + }, + }, + cardSubtitle: 'Qin Dynasty unifies China under Emperor Qin Shi Huang', + cardDetailedText: [ + 'The Qing dynasty was the last imperial dynasty in Chinese history, ruling from 1644 to 1912. Its origins can be traced back to the early 17th century when a Chinese rebel leader named Li Zicheng overthrew the Ming dynasty and established the short-lived Shun dynasty.', + 'In 1644, a Ming general named Wu Sangui made a deal with the Manchu, a tribal people from the northeast, to help him fight against Li Zicheng. The Manchu forces, led by Prince Dorgon, defeated Li Zicheng and his rebel army, but instead of handing power back to the Ming, they established their own dynasty.', + 'The early years of the Qing dynasty were marked by efforts to consolidate power and establish control over the vast territories they had conquered. The Manchu rulers adopted many of the institutions and practices of the Ming dynasty and made efforts to win the support of the Chinese people.', + "One of the most significant changes made by the Qing was the introduction of the queue hairstyle, which required all men to shave the front of their heads and wear their hair in a long braid, as a symbol of submission to Manchu authority. The Qing also expanded China's territory through military conquests and established a powerful and centralized government.", + 'Despite its efforts to win the support of the Chinese people, the Qing dynasty faced growing opposition and unrest in the 19th century. The dynasty was weakened by corruption, economic stagnation, and foreign imperialism, and faced several major rebellions and uprisings, including the Taiping Rebellion and the Boxer Rebellion.', + 'The Qing dynasty came to an end in 1912 when the last emperor, Puyi, abdicated the throne following the Xinhai Revolution. The fall of the Qing marked the end of imperial rule in China and the beginning of a new era of republicanism.', + ], + }, + { + title: '1 AD', + url: 'https://en.wikipedia.org/wiki/Common_Era', + cardTitle: 'Common Era', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/53/130653-050-532C1F10/clock-St-John-the-Baptist-cathedral-Lyon-2019.jpg', + }, + }, + cardSubtitle: 'Start of the Common Era (CE) in the Gregorian calendar', + cardDetailedText: [ + 'The Common Era, also known as the Christian Era or the Current Era, is the calendar era that has been used since the birth of Jesus of Nazareth.', + "The exact year of Jesus' birth is unknown, but most scholars believe he was born in or around the year 4 BCE. The year of his birth is considered the starting point of the Common Era, and is denoted as '1 CE' or 'AD 1' (Anno Domini, which means 'in the year of our Lord' in Latin).", + 'The Common Era is based on the Gregorian calendar, which was introduced by Pope Gregory XIII in 1582 as a reform of the Julian calendar. The Gregorian calendar is a solar calendar that is based on the length of the tropical year, which is the time it takes for the Earth to complete one orbit around the sun.', + "The Common Era is the most widely used calendar system in the world today, and is the international standard for civil calendars. It is used by most of the world's countries, including the United States, Canada, and the United Kingdom.", + ], + }, + { + title: '476 AD', + url: 'https://en.wikipedia.org/wiki/Fall_of_the_Western_Roman_Empire', + cardTitle: 'Western Roman Empire', + media: { + type: 'IMAGE', + source: { + url: 'https://i0.wp.com/thinkingwest.com/wp-content/uploads/2022/03/50117621663_2b17d3b477_h.jpg?fit=1438%2C898&ssl=1', + }, + }, + cardSubtitle: 'Fall of the Western Roman Empire', + cardDetailedText: [ + 'The Western Roman Empire, which had been in decline for centuries, finally fell in 476 CE.', + 'The decline of the Western Roman Empire can be attributed to a variety of factors, including economic troubles, overreliance on slave labor, and military overspending.', + 'Throughout the 4th and 5th centuries, the Western Roman Empire faced numerous invasions from barbarian tribes such as the Visigoths, Huns, and Vandals. These invasions, combined with internal struggles for power, weakened the empire and left it vulnerable to collapse.', + 'In 476 CE, the last Western Roman Emperor, Romulus Augustus, was deposed by the Germanic warlord Odoacer, who declared himself king of Italy. This event marked the end of the Western Roman Empire and the beginning of the Middle Ages in Europe.', + 'The fall of the Western Roman Empire had a profound impact on the course of Western history. It led to the fragmentation of Western Europe into smaller, more localized kingdoms and paved the way for the rise of new powers such as the Byzantine Empire and the Islamic Caliphate.', + 'Despite the fall of the Western Roman Empire, the legacy of Rome continued to have a significant impact on Western civilization. Roman law, language, and culture persisted throughout Europe and influenced the development of modern Western society.', + ], + }, + { + title: '1096-1270', + url: 'https://en.wikipedia.org/wiki/Crusades', + cardTitle: 'The Crusades', + cardSubtitle: 'The Crusades', + media: { + type: 'IMAGE', + source: { + url: 'https://api.time.com/wp-content/uploads/2019/10/crusaders-right-wing-01.jpg', + }, + }, + cardDetailedText: [ + 'The Crusades were a series of religious wars fought between Christian and Muslim forces in the Middle Ages.', + 'The first Crusade was launched in 1096 in response to a call for help from the Byzantine Emperor, who was facing threats from Seljuk Turks in Anatolia. The Crusaders successfully captured Jerusalem in 1099 and established a Christian kingdom in the region.', + 'Several other Crusades followed over the next two centuries, with varying degrees of success. Some Crusades were aimed at recapturing Jerusalem and other holy sites from Muslim control, while others were launched for political or economic reasons.', + 'The Crusades had a profound impact on the history of Europe and the Middle East. They brought about significant cultural and technological exchanges between the two regions and helped to shape the development of Western civilization. They also led to the rise of new powers in the Middle East, such as the Ayyubid and Mamluk dynasties.', + 'However, the Crusades also had a dark side. They were marked by atrocities committed by both Christian and Muslim forces, and they helped to deepen the divide between the Christian and Muslim worlds.', + 'The legacy of the Crusades continues to be debated by historians and scholars today. Some view them as a noble struggle to defend Christendom, while others see them as an example of religious intolerance and imperialism.', + ], + }, + { + title: '1215', + url: 'https://en.wikipedia.org/wiki/Magna_Carta', + cardTitle: 'England', + cardSubtitle: + 'Signing of the Magna Carta in England, establishing the principle of constitutional monarchy', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/28/144228-050-FB55C473/engraving-King-John-Magna-Carta-Runnymede-England-June-15-1215.jpg', + }, + }, + cardDetailedText: [ + 'The Magna Carta, also known as the Great Charter, is a document signed by King John of England in 1215.', + "The Magna Carta was created in response to a political crisis in England. The barons, who were powerful nobles, were unhappy with King John's rule and demanded greater rights and freedoms. In order to avoid a civil war, King John agreed to sign the Magna Carta.", + 'The Magna Carta established several key principles that are still influential today. These principles include the idea that everyone is subject to the law, even the king, and that people have certain basic rights, such as the right to a fair trial.', + 'The Magna Carta also limited the power of the king and established the principle that the monarch must govern according to the law. This idea became an important part of the English legal system and helped to shape the development of modern democracy.', + 'Although the Magna Carta was originally intended to benefit the barons, its principles eventually became more widely applicable. Over time, the idea that everyone has basic rights and freedoms became an important part of Western political philosophy.', + 'Today, the Magna Carta is seen as a landmark document in the history of democracy and human rights. Its principles have been enshrined in many legal documents, including the United States Constitution and the Universal Declaration of Human Rights.', + ], + }, + { + title: '1347-1351', + url: 'https://en.wikipedia.org/wiki/Black_Death', + cardTitle: 'The Black Death', + cardSubtitle: + 'The Black Death (Bubonic Plague) kills an estimated 75-200 million people in Eurasia', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/60/161060-050-D01431DF/Flagellants-Black-Death-Netherlands-atonement-sins-God.jpg', + }, + }, + cardDetailedText: [ + 'The Black Death, also known as the Bubonic Plague, was a pandemic that swept through Europe and Asia in the 14th century.', + 'The Black Death was caused by a bacterium called Yersinia pestis, which was transmitted by fleas that infested rats. The disease spread rapidly, and it is estimated that it killed between 75 and 200 million people.', + 'The Black Death had a profound impact on the social and economic systems of Europe. It led to a shortage of labor, which caused wages to rise and eventually led to the decline of feudalism. It also contributed to the rise of a middle class and the growth of cities.', + 'The Black Death also had a significant impact on art and literature. Many artists and writers depicted death and suffering in their work, and the idea of the danse macabre, or dance of death, became popular.', + 'The Black Death had long-term effects on medical knowledge and public health. It led to advances in the understanding of infectious diseases and the development of quarantine practices.', + 'Although the Black Death was a devastating event, it had some positive consequences as well. It led to social and economic changes that paved the way for the Renaissance and the Age of Exploration.', + 'Today, the Black Death serves as a reminder of the fragility of human life and the importance of public health measures in controlling the spread of disease.', + ], + }, + { + title: '1455-1485', + url: 'https://en.wikipedia.org/wiki/Wars_of_the_Roses', + cardTitle: 'Wars of the Roses', + cardSubtitle: 'Wars of the Roses in England', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/72/100472-050-1D4EC299/Illustration-Richard-III-Battle-of-Bosworth-Field.jpg', + }, + }, + cardDetailedText: [ + 'The Wars of the Roses were a series of civil wars fought in England in the 15th century between the House of Lancaster and the House of York.', + 'The conflict began in 1455, when Richard, Duke of York, challenged King Henry VI for the throne. Over the next three decades, the two houses fought for control of the monarchy, with each side winning and losing battles.', + "The name 'Wars of the Roses' comes from the symbols used by the two houses. The House of Lancaster used a red rose as its symbol, while the House of York used a white rose.", + 'The Wars of the Roses came to an end in 1485 with the victory of Henry Tudor, who was backed by the House of Lancaster. Henry Tudor became King Henry VII, and he united the two houses by marrying Elizabeth of York, the daughter of Edward IV.', + 'The Wars of the Roses had a profound impact on English history. They weakened the power of the nobility, paved the way for the centralization of power in the monarchy, and contributed to the rise of the Tudor dynasty.', + "The Wars of the Roses also had an impact on English culture. They inspired many works of literature, including the plays of William Shakespeare, and they gave rise to the concept of the 'hero king', a ruler who embodies the virtues of courage and justice.", + ], + }, + { + title: '1492', + url: 'https://en.wikipedia.org/wiki/Christopher_Columbus', + cardTitle: 'Christopher Columbus', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/69/166769-050-3F2D90E7/Christopher-Columbus-Print-Isabella-I-bidding-farewell-August-3-1492.jpg', + }, + }, + cardSubtitle: + 'Christopher Columbus makes his first voyage to the Americas, initiating European colonization of the New World', + cardDetailedText: [ + 'Christopher Columbus was an Italian explorer who is credited with discovering the New World.', + 'Columbus was born in Genoa, Italy, in 1451. He began his career as a sailor when he was a teenager, and he quickly became an expert navigator and cartographer.', + "In 1492, Columbus set out on a voyage across the Atlantic Ocean, seeking a new route to Asia. Instead, he landed in the Bahamas, which he called the 'New World'. He made three more voyages to the Americas, exploring the Caribbean, Central, and South America.", + "Columbus's voyages had a profound impact on the world. They led to the Columbian Exchange, a widespread exchange of plants, animals, and diseases between the Old World and the New World. Columbus's voyages also sparked a period of exploration and colonization of the Americas by Europeans.", + "However, Columbus's legacy is controversial. He is criticized for his treatment of indigenous peoples, who were subjected to forced labor and violence under Spanish colonial rule. Columbus's arrival in the Americas also had a devastating impact on the indigenous population, leading to the spread of disease and the displacement of communities.", + 'Despite this, Columbus is still celebrated as a hero in many parts of the world. His voyages are seen as a symbol of human curiosity, exploration, and discovery, and his legacy has had a lasting impact on global history.', + ], + }, + { + title: '1517', + url: 'https://en.wikipedia.org/wiki/Martin_Luther', + cardTitle: 'Martin Luther', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/97/115497-050-7D8CC20E/Portrait-oil-panel-Martin-Luther-Lucas-Cranach.jpg?w=400&h=300&c=crop', + }, + }, + cardSubtitle: `Martin Luther’s Ninety-Five Theses sparks the Protestant Reformation in Europe`, + cardDetailedText: [ + 'Martin Luther was a German monk and theologian who played a key role in the Protestant Reformation.', + "Luther was born in Eisleben, Germany, in 1483. He became a monk and was ordained as a priest in the Catholic Church in 1507. He taught theology at the University of Wittenberg, where he became increasingly critical of the Catholic Church's teachings and practices.", + "In 1517, Luther wrote and posted his 'Ninety-Five Theses' on the door of the Castle Church in Wittenberg. This document criticized the Church's sale of indulgences and called for reform of the Church's practices.", + "Luther's ideas quickly spread throughout Germany and Europe, leading to the establishment of the Protestant Church. He translated the Bible into German, making it accessible to ordinary people for the first time.", + "Luther's teachings had a profound impact on Western culture and history. They led to the formation of new religious denominations and challenged the authority of the Catholic Church. They also contributed to the development of modern democracy and individualism.", + "Luther's legacy continues to be debated and celebrated today. He is remembered as a revolutionary thinker who stood up for his beliefs and sparked a religious and cultural revolution in Europe.", + ], + }, + { + title: '1789', + url: 'https://en.wikipedia.org/wiki/French_Revolution', + cardTitle: 'French Revolution', + media: { + type: 'IMAGE', + source: { + url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/Anonymous_-_Prise_de_la_Bastille.jpg/1200px-Anonymous_-_Prise_de_la_Bastille.jpg', + }, + }, + cardSubtitle: + 'French Revolution begins, leading to the overthrow of the French monarchy and the rise of Napoleon Bonaparte', + cardDetailedText: [ + 'The French Revolution was a period of radical social and political upheaval in France from 1789 to 1799.', + 'The revolution began with the storming of the Bastille on July 14, 1789, and quickly spread throughout France. The revolution was fueled by Enlightenment ideals of liberty, equality, and fraternity, and was marked by the Reign of Terror, a period of mass executions and political violence.', + 'The revolution brought about significant social and political changes in France. It abolished the monarchy and established a republic, and it also paved the way for the rise of Napoleon Bonaparte and the Napoleonic Wars.', + 'The revolution had a lasting impact on world history. It inspired other revolutionary movements around the world and contributed to the spread of democracy and nationalism.', + 'However, the revolution also had its drawbacks. It was marked by widespread violence and bloodshed, and many of its ideals were not fully realized for many years after the revolution ended.', + 'Today, the French Revolution is remembered as a pivotal moment in the history of France and the world. Its legacy continues to be debated and celebrated, and its ideals of liberty, equality, and fraternity continue to inspire people around the world.', + ], + }, + { + title: '1861-1865', + url: 'https://en.wikipedia.org/wiki/American_Civil_War', + cardTitle: 'American Civil War', + cardSubtitle: 'American Civil War', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/13/149613-050-C0B0B8D5/Battle-of-Gettysburg-Currier-lithograph-Ives-July-3-1863.jpg', + }, + }, + cardDetailedText: [ + 'The American Civil War was a conflict fought in the United States from 1861 to 1865.', + 'The war was fought between the Northern states, known as the Union, and the Southern states, known as the Confederacy. It was primarily fought over the issue of slavery and the political power of the Southern states.', + 'The war began on April 12, 1861, when Confederate forces attacked a United States military installation at Fort Sumter, South Carolina. The war was marked by several major battles, including the Battle of Gettysburg and the Siege of Vicksburg.', + 'The Union ultimately emerged victorious, and the Confederacy was dissolved. The war resulted in the abolition of slavery and the reunification of the United States.', + 'The American Civil War had a profound impact on the United States and its history. It led to significant social and political changes, including the adoption of the 13th, 14th, and 15th Amendments to the Constitution.', + 'The war also had a lasting impact on American culture and society, and its legacy continues to be debated and studied today.', + ], + }, + { + title: '1914-1918', + url: 'https://en.wikipedia.org/wiki/World_War_I', + cardTitle: 'World War I', + cardSubtitle: 'World War I', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/44/65944-050-F18FEEA4/soldier-British-trench-Western-Front-World-War.jpg', + }, + }, + cardDetailedText: [ + 'World War 1, also known as the Great War, was a global conflict that lasted from 1914 to 1918.', + 'The war was fought between two main alliances: the Allied Powers, which consisted of France, Russia, and the United Kingdom, among others, and the Central Powers, which included Germany, Austria-Hungary, and the Ottoman Empire.', + 'The war was sparked by the assassination of Archduke Franz Ferdinand of Austria-Hungary on June 28, 1914. The conflict quickly spread throughout Europe and eventually grew into a global war.', + 'World War 1 was marked by several major battles, including the Battle of the Somme and the Battle of Verdun. It also saw the introduction of new weapons and tactics, including tanks and trench warfare.', + 'The war ended on November 11, 1918, when the Allied Powers signed an armistice with Germany. The war resulted in the deaths of millions of people and had a profound impact on the world.', + 'The Treaty of Versailles, which was signed in 1919, officially ended the war and imposed harsh penalties on Germany. The aftermath of World War 1 set the stage for World War 2, which began in 1939.', + ], + }, + { + title: '1939-1945', + url: 'https://en.wikipedia.org/wiki/World_War_II', + cardTitle: 'World War II', + cardSubtitle: 'World War II', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.britannica.com/26/188426-050-2AF26954/Germany-Poland-September-1-1939.jpg', + }, + }, + cardDetailedText: [ + 'World War 2 was a global conflict that lasted from 1939 to 1945.', + "The war involved the majority of the world's nations, including all of the great powers, organized into two opposing military alliances: the Allies and the Axis.", + "The war was started by Germany's invasion of Poland on September 1, 1939, and quickly spread to other parts of Europe, Africa, and Asia.", + 'The war saw several major battles and campaigns, including the Battle of Stalingrad, the Normandy landings, and the Battle of Midway.', + 'World War 2 was marked by several war crimes and atrocities, including the Holocaust, in which an estimated 6 million Jews were murdered by the Nazis.', + 'The war ended on September 2, 1945, when Japan formally surrendered to the Allied powers. The war had a profound impact on the world, leading to the formation of the United Nations and the beginning of the Cold War.', + 'The aftermath of the war saw the division of Germany and the beginning of the nuclear arms race, as well as significant changes in global politics and the balance of power.', + ], + }, + { + title: '1945', + url: 'https://en.wikipedia.org/wiki/United_Nations', + cardTitle: 'United Nations', + cardSubtitle: 'The United Nations is founded', + media: { + type: 'IMAGE', + source: { + url: 'https://upload.wikimedia.org/wikipedia/commons/2/2f/Flag_of_the_United_Nations.svg', + }, + }, + cardDetailedText: [ + 'The United Nations (UN) is an intergovernmental organization founded on October 24, 1945, after the end of World War 2.', + 'The organization aims to promote international cooperation and maintain international peace and security.', + 'It has 193 member states and its headquarters are in New York City.', + 'The UN has several bodies and agencies, including the General Assembly, the Security Council, the International Court of Justice, and the World Health Organization.', + 'The UN has played a major role in several international crises and conflicts, including the Korean War, the Gulf War, and the Syrian Civil War.', + 'The organization has also been involved in several humanitarian and development initiatives, including the Millennium Development Goals and the Sustainable Development Goals.', + 'The UN has been criticized for its perceived ineffectiveness and bureaucracy, as well as its failure to prevent or end several conflicts and crises around the world.', + ], + }, + { + title: '1945-1991', + url: 'https://en.wikipedia.org/wiki/Cold_War', + cardTitle: 'Cold War', + cardSubtitle: 'Cold War', + media: { + type: 'IMAGE', + source: { + url: 'https://www.thoughtco.com/thmb/f-7yINXg4azNYn1Oq2PbWC7DrlI=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/GettyImages-184282094-58e1511b3df78c5162a84406.jpg', + }, + }, + cardDetailedText: [ + 'The Cold War was a geopolitical conflict between the United States and the Soviet Union and their respective allies, lasting from the end of World War 2 in 1945 until the early 1990s.', + 'The conflict was characterized by political and military tension, proxy wars, and a nuclear arms race.', + 'The origins of the Cold War can be traced back to the end of World War 2, when the United States and the Soviet Union emerged as the two superpowers of the world.', + 'The conflict was marked by several major events, including the Cuban Missile Crisis, the Vietnam War, and the Soviet invasion of Afghanistan.', + 'The Cold War had a significant impact on global politics and the balance of power, as well as on popular culture and society.', + "The conflict ended in the early 1990s, following the collapse of the Soviet Union and the emergence of the United States as the world's sole superpower.", + 'The legacy of the Cold War continues to influence international relations and global politics today.', + ], + }, + { + title: '1969', + url: 'https://en.wikipedia.org/wiki/Apollo_11', + cardTitle: 'Apollo 11', + media: { + type: 'IMAGE', + source: { + url: 'https://cdn.mos.cms.futurecdn.net/3CorBj4FSCunixkTsDZy5L.jpg', + }, + }, + cardSubtitle: 'Apollo 11 lands on the moon', + cardDetailedText: [ + 'Apollo 11 was the spaceflight that first landed humans on the Moon, on July 20, 1969.', + "The mission was carried out by the United States' National Aeronautics and Space Administration (NASA) and was the fifth crewed mission of NASA's Apollo program.", + 'The crew of Apollo 11 consisted of Neil Armstrong, Buzz Aldrin, and Michael Collins.', + 'Armstrong and Aldrin became the first humans to walk on the Moon, while Collins orbited above.', + 'The mission was a major achievement for the United States in the Cold War Space Race against the Soviet Union.', + 'The event was watched by an estimated 650 million people worldwide and is widely considered to be one of the greatest achievements in human history.', + 'After the mission, the crew of Apollo 11 were celebrated as heroes and received ticker-tape parades in their honor.', + ], + }, +]; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
App.styles.ts | +
+
+ |
+ 0% | +0/184 | +0% | +0/1 | +0% | +0/1 | +0% | +0/184 | +
App.tsx | +
+
+ |
+ 0% | +0/253 | +0% | +0/1 | +0% | +0/1 | +0% | +0/253 | +
data-mixed.ts | +
+
+ |
+ 0% | +0/188 | +0% | +0/1 | +0% | +0/1 | +0% | +0/188 | +
data-nested.ts | +
+
+ |
+ 0% | +0/200 | +0% | +0/1 | +0% | +0/1 | +0% | +0/200 | +
data.tsx | +
+
+ |
+ 0% | +0/260 | +0% | +0/1 | +0% | +0/1 | +0% | +0/260 | +
data2.ts | +
+
+ |
+ 0% | +0/111 | +0% | +0/1 | +0% | +0/1 | +0% | +0/111 | +
dynamic-load.tsx | +
+
+ |
+ 0% | +0/67 | +0% | +0/1 | +0% | +0/1 | +0% | +0/67 | +
horizontal-samples.tsx | +
+
+ |
+ 0% | +0/166 | +0% | +0/1 | +0% | +0/1 | +0% | +0/166 | +
human-history.tsx | +
+
+ |
+ 0% | +0/399 | +0% | +0/1 | +0% | +0/1 | +0% | +0/399 | +
layout.tsx | +
+
+ |
+ 0% | +0/76 | +0% | +0/1 | +0% | +0/1 | +0% | +0/76 | +
vertical-samples.tsx | +
+
+ |
+ 0% | +0/610 | +0% | +0/1 | +0% | +0/1 | +0% | +0/610 | +
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import cls from 'classnames'; +import React from 'react'; +import { NavLink, Outlet } from 'react-router-dom'; +import styles from './layout.module.scss'; + +const items = [ + { path: '/vertical-basic', label: 'Vertical Basic' }, + { + path: '/vertical-basic-nested', + label: 'Vertical Basic Nested', + hoverClass: 'hover:bg-blue-600', + }, + { path: '/vertical-world-history', label: 'Vertical World History' }, + { path: '/vertical-alternating-mixed', label: 'Vertical Alternating Mixed' }, + { + path: '/vertical-alternating-nested', + label: 'Vertical Alternating Nested', + }, + { path: '/vertical-alternating', label: 'Vertical Alternating' }, + { path: '/horizontal', label: 'Horizontal' }, + { path: '/horizontal-all', label: 'Horizontal All' }, + { path: '/horizontal-initial-select', label: 'Horizontal Initial Select' }, + { path: '/vertical-custom', label: 'Vertical Custom' }, + { path: '/vertical-custom-icon', label: 'Vertical Custom Icon' }, + { path: '/dynamic-load', label: 'Dynamic Load' }, + { + path: '/timeline-without-cards', + label: 'Timeline Without Cards (Vertical)', + }, + { + path: '/timeline-without-cards-horizontal', + label: 'Timeline Without Cards (Horizontal)', + }, +]; + +const Layout = () => { + return ( + <div className={styles.wrapper}> + <header + className={cls([ + 'bg-gray-900', + 'text-white', + 'px-4', + 'py-2', + styles.header, + 'rounded', + ])} + > + <h2 className={cls('text-3xl font-bold')}> + React-Chrono (Kitchen Sink) + </h2> + </header> + <div className={styles.container}> + <aside className={cls(styles.aside, ['bg-gray-100', 'p-4', 'sm:hide'])}> + <ul className={cls(['list-none', 'list-inside'])}> + {items.map((item, index) => ( + <li + key={index} + className={cls('m-1', 'py-1', 'pl-1', 'hover:text-blue-800')} + > + <NavLink to={item.path} style={{ height: '100%' }}> + {item.label} + </NavLink> + </li> + ))} + </ul> + </aside> + <div className={styles.content}> + <Outlet /> + </div> + </div> + </div> + ); +}; + +export { Layout }; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { Theme } from '@models/Theme'; +import { TimelineItemModel } from '@models/TimelineItemModel'; +import React, { FunctionComponent, useState } from 'react'; +import Chrono from '../components'; +import { ComponentContainerTree, Vertical } from './App.styles'; +import data from './data'; +import dataMixed from './data-mixed'; + +export const VerticalTree: FunctionComponent<{ + type: string; + items: TimelineItemModel[]; + theme: Theme; +}> = ({ type, items, theme }) => { + return ( + <Vertical id="tree"> + <ComponentContainerTree type={type}> + <Chrono + items={items} + mode="VERTICAL_ALTERNATING" + theme={theme} + slideShow + slideItemDuration={2050} + slideShowType="slide_from_sides" + allowDynamicUpdate + cardHeight={300} + // textOverlay + focusActiveItemOnLoad + enableDarkToggle + cardWidth={450} + onItemSelected={(selected) => console.log(selected)} + onScrollEnd={() => console.log('end reached')} + verticalBreakPoint={1920} + enableBreakPoint + > + <div className="chrono-icons"> + <img + src="https://img.icons8.com/ios-filled/100/000000/twitter.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/about.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/contacts.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/briefcase.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/idea.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/sun.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/info.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/calendar.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/50/000000/mailbox-closed-flag-down.png" + alt="mail-box" + /> + <img + src="https://img.icons8.com/ios-filled/50/000000/pinterest.png" + alt="pinterest" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/reddit.png" + alt="reddit" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/facebook.png" + alt="reddit" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/stumbleupon.png" + alt="reddit" + /> + </div> + </Chrono> + </ComponentContainerTree> + </Vertical> + ); +}; + +export const VerticalTreeMixed: FunctionComponent<{ + type: string; + cardHeight?: number; +}> = ({ type, cardHeight }) => ( + <Vertical> + <ComponentContainerTree type={type}> + <Chrono + items={dataMixed} + mode="VERTICAL_ALTERNATING" + cardHeight={300} + cardWidth={450} + scrollable + slideShow + slideItemDuration={2500} + enableDarkToggle + /> + </ComponentContainerTree> + </Vertical> +); + +export const VerticalBasic: FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ type, items }) => ( + <Vertical id="vertical"> + <ComponentContainerTree type={type}> + <Chrono + items={items} + mode="VERTICAL" + slideShow + cardWidth={650} + slideItemDuration={2500} + scrollable={{ scrollbar: false }} + noUniqueId + uniqueID="vertical_basic_test" + parseDetailsTextHTML + disableInteraction + // textOverlay + // borderLessCards + // theme={{ + // cardBgColor: '#fff', + // titleColorActive: '#6495ed', + // titleColor: '#922724', + // cardDetailsBackGround: '#e8e8e8', + // }} + onItemSelected={(selected) => console.log(selected.cardTitle)} + enableOutline + fontSizes={{ + title: '1.5rem', + }} + theme={{ + cardDetailsColor: '#555555', + }} + // flipLayout + focusActiveItemOnLoad + activeItemIndex={2} + // mediaHeight={150} + // nestedCardHeight={100} + cardHeight={200} + contentDetailsHeight={10} + timelinePointDimension={20} + classNames={{ + cardText: 'custom-text', + }} + mediaSettings={{ + imageFit: 'cover', + }} + enableDarkToggle + /> + </ComponentContainerTree> + </Vertical> +); + +export const VerticalNewMedia: FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ type, items }) => { + return ( + <> + <Vertical id="vertical"> + <ComponentContainerTree type={type}> + <Chrono + items={items} + mode="VERTICAL_ALTERNATING" + slideShow + showAllCardsHorizontal + cardWidth={450} + slideItemDuration={2000} + scrollable={{ scrollbar: false }} + textOverlay + // slideShowType="reveal" + // borderLessCards + // theme={{ + // cardBgColor: '#fff', + // titleColorActive: '#6495ed', + // titleColor: '#922724', + // cardDetailsBackGround: '#e8e8e8', + // cardDetailsColor: '#000', + // }} + // darkMode + onItemSelected={(selected) => console.log(selected.cardTitle)} + // enableOutline + fontSizes={{ + title: '1.5rem', + }} + theme={{ + cardDetailsColor: '#2f4f4f', + }} + cardHeight={350} + // timelinePointShape="diamond" + focusActiveItemOnLoad + activeItemIndex={9} + // mediaHeight={200} + // cardHeight={250} + // cardHeight={350} + enableDarkToggle + contentDetailsHeight={200} + timelinePointDimension={20} + classNames={{ + cardText: 'custom-text', + }} + // mediaSettings={{ + // imageFit: 'cover', + // }} + /> + </ComponentContainerTree> + </Vertical> + </> + ); +}; +export const VerticalAlternatingNested: FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ type, items }) => { + return ( + <> + <Vertical id="vertical"> + <ComponentContainerTree type={type}> + <Chrono + items={items} + mode="VERTICAL_ALTERNATING" + slideShow + cardWidth={600} + slideItemDuration={2000} + scrollable={{ scrollbar: false }} + slideShowType="slide_from_sides" + mediaSettings={{ + imageFit: 'contain', + }} + highlightCardsOnHover + // borderLessCards + // theme={{ + // cardBgColor: '#fff', + // titleColorActive: '#6495ed', + // titleColor: '#922724', + // cardDetailsBackGround: '#e8e8e8', + // cardDetailsColor: '#000', + // }} + // darkMode + onItemSelected={(selected) => console.log(selected.cardTitle)} + // enableOutline + fontSizes={{ + title: '1rem', + }} + theme={{ + primary: '#191919', + secondary: '#FFA500', + titleColor: '#FFA500', + titleColorActive: '#000', + cardTitleColor: '#FFA500', + iconBackgroundColor: '#fff', + }} + cardHeight={150} + timelinePointShape="square" + // focusActiveItemOnLoad + // activeItemIndex={9} + // cardHeight={250} + // cardHeight={350} + mediaHeight={200} + enableDarkToggle + timelinePointDimension={30} + classNames={{ + cardText: 'custom-text', + }} + > + <div className="chrono-icons"> + <img + src="https://img.icons8.com/ios-filled/100/000000/twitter.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/about.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/contacts.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/briefcase.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/idea.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/sun.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/info.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/calendar.png" + alt="twitter" + /> + <img + src="https://img.icons8.com/ios-filled/50/000000/mailbox-closed-flag-down.png" + alt="mail-box" + /> + <img + src="https://img.icons8.com/ios-filled/50/000000/pinterest.png" + alt="pinterest" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/reddit.png" + alt="reddit" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/facebook.png" + alt="reddit" + /> + <img + src="https://img.icons8.com/ios-filled/100/000000/stumbleupon.png" + alt="reddit" + /> + </div> + </Chrono> + </ComponentContainerTree> + </Vertical> + </> + ); +}; + +export const VerticalBasicCardLess: FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ type, items }) => ( + <Vertical id="vertical"> + <ComponentContainerTree type={type}> + <Chrono + items={items} + mode="VERTICAL" + cardLess + theme={{ + cardBgColor: '#fff', + titleColorActive: 'red', + }} + onItemSelected={(selected) => console.log(selected.cardTitle)} + /> + </ComponentContainerTree> + </Vertical> +); + +export const VerticalBasicNested: FunctionComponent<{ + type: string; + items: TimelineItemModel[]; +}> = ({ type, items }) => ( + <Vertical id="vertical"> + <ComponentContainerTree type={type}> + <Chrono + items={items} + mode="HORIZONTAL" + slideShow + cardWidth={500} + slideItemDuration={2500} + scrollable={{ scrollbar: false }} + // textOverlay + // borderLessCards + // theme={{ + // cardBgColor: '#fff', + // titleColorActive: '#6495ed', + // titleColor: '#922724', + // cardDetailsBackGround: '#e8e8e8', + // }} + onItemSelected={(selected) => console.log(selected.cardTitle)} + enableOutline + fontSizes={{ + title: '1rem', + }} + // flipLayout + focusActiveItemOnLoad + activeItemIndex={2} + mediaHeight={200} + nestedCardHeight={100} + cardHeight={300} + // contentDetailsHeight={300} + timelinePointDimension={20} + // timelinePointShape="diamond" + classNames={{ + cardText: 'custom-text', + }} + enableDarkToggle + mediaSettings={{ align: 'center' }} + /> + </ComponentContainerTree> + </Vertical> +); + +export const VerticalCustomContent2: FunctionComponent<{ + type: string; + cardHeight?: number; + items?: TimelineItemModel[]; +}> = ({ type, cardHeight, items }) => ( + <Vertical> + <ComponentContainerTree type={type}> + <Chrono + mode="VERTICAL" + cardHeight={200} + cardWidth={650} + scrollable + flipLayout + timelinePointDimension={30} + items={items} + > + <div> + <div style={{ width: '250px', height: '250px' }}> + <img + style={{ maxWidth: '100%', maxHeight: '100%' }} + src="https://cdn.tutsplus.com/net/uploads/2013/08/github-collab-retina-preview.gif" + alt="github" + /> + </div> + </div> + <div> + <h3>This is a List</h3> + <ul> + <li>Item 1</li> + <li>Item 2</li> + <li>Item 3</li> + <li>Item 4</li> + <li>Item 5</li> + </ul> + </div> + <div> + <h3>Dunkirk</h3> + <p> + The Battle of Dunkirk (French: Bataille de Dunkerque) was fought in + Dunkirk (Dunkerque), France, during the Second World War, between + the Allies and Nazi Germany. As the Allies were losing the Battle of + France on the Western Front, the Battle of Dunkirk was the defence + and evacuation to Britain of British and other Allied forces in + Europe from 26 May to 4 June 1940. + </p> + <p> + After the Phoney War, the Battle of France began in earnest on 10 + May 1940. To the east, the German Army Group B invaded the + Netherlands and advanced westward. In response, the Supreme Allied + Commander—French General Maurice Gamelin—initiated "Plan D" and + entered Belgium to engage the Germans in the Netherlands. The plan + relied heavily on the Maginot Line fortifications along the + German–French border, but German forces had already crossed through + most of the Netherlands before the French forces arrived. Gamelin + instead committed the forces under his command, three mechanised + armies, the French First and Seventh Armies and the British + Expeditionary Force (BEF), to the River Dyle. + </p> + </div> + <div style={{ margin: '1rem' }}> + <h3>Table</h3> + <table> + <thead> + <tr> + <td>Column 1</td> + <td>Column 2</td> + <td>Column 3</td> + <td>Column 4</td> + </tr> + </thead> + <tbody> + <tr> + <td>Value 1</td> + <td>Value 2</td> + <td>Value 3</td> + <td>Value 4</td> + </tr> + <tr> + <td>Value 5</td> + <td>Value 6</td> + <td>Value 7</td> + <td>Value 8</td> + </tr> + </tbody> + </table> + </div> + <div className="chrono-icons"> + <img src="satellite-dish.svg" alt="github" /> + <img src="notification-bell.svg" alt="github" /> + <img src="camera.svg" alt="github" /> + <img src="rss.svg" alt="github" /> + </div> + </Chrono> + </ComponentContainerTree> + </Vertical> +); + +export const VerticalTreeSlideshow: FunctionComponent<{ + type: string; + cardHeight: number; +}> = ({ type, cardHeight }) => ( + <Vertical> + <ComponentContainerTree type={type}> + <Chrono + items={data} + mode="VERTICAL_ALTERNATING" + cardHeight={200} + scrollable + slideShow + // theme={{ primary: '#8675a9', secondary: '#ffd5cd' }} + /> + </ComponentContainerTree> + </Vertical> +); + +export const VerticalCustomContent: FunctionComponent<{ + type: string; + cardHeight?: number; +}> = ({ type, cardHeight }) => { + const [counter, setCounter] = useState(0); + + const increment = () => setCounter((prev) => prev + 1); + const decrement = () => setCounter((prev) => prev - 1); + + return ( + <Vertical> + <ComponentContainerTree type={type}> + <Chrono mode="VERTICAL" cardHeight={200} cardWidth={650} scrollable> + <div> + <div style={{ width: '250px', height: '250px' }}> + <img + style={{ maxWidth: '100%', maxHeight: '100%' }} + src="https://cdn.tutsplus.com/net/uploads/2013/08/github-collab-retina-preview.gif" + alt="github" + /> + </div> + </div> + <div> + <h3>This is a List</h3> + {/* <ul> + <li>Item 1</li> + <li>Item 2</li> + <li>Item 3</li> + <li>Item 4</li> + </ul> */} + {counter} + <button onClick={increment}>increment</button> + <button onClick={decrement}>decrement</button> + </div> + <div> + <h3>Dunkirk</h3> + <p> + The Battle of Dunkirk (French: Bataille de Dunkerque) was fought + in Dunkirk (Dunkerque), France, during the Second World War, + between the Allies and Nazi Germany. As the Allies were losing the + Battle of France on the Western Front, the Battle of Dunkirk was + the defence and evacuation to Britain of British and other Allied + forces in Europe from 26 May to 4 June 1940. + </p> + <p> + After the Phoney War, the Battle of France began in earnest on 10 + May 1940. To the east, the German Army Group B invaded the + Netherlands and advanced westward. In response, the Supreme Allied + Commander—French General Maurice Gamelin—initiated "Plan D" and + entered Belgium to engage the Germans in the Netherlands. The plan + relied heavily on the Maginot Line fortifications along the + German–French border, but German forces had already crossed + through most of the Netherlands before the French forces arrived. + Gamelin instead committed the forces under his command, three + mechanised armies, the French First and Seventh Armies and the + British Expeditionary Force (BEF), to the River Dyle. + </p> + </div> + <div style={{ margin: '1rem' }}> + <h3>Table</h3> + <table> + <thead> + <tr> + <td>Column 1</td> + <td>Column 2</td> + <td>Column 3</td> + <td>Column 4</td> + </tr> + </thead> + <tbody> + <tr> + <td>Value 1</td> + <td>Value 2</td> + <td>Value 3</td> + <td>Value 4</td> + </tr> + <tr> + <td>Value 5</td> + <td>Value 6</td> + <td>Value 7</td> + <td>Value 8</td> + </tr> + </tbody> + </table> + </div> + </Chrono> + </ComponentContainerTree> + </Vertical> + ); +}; + |
+ 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 | + + + + + + + + + | import Chrono from '../components/index'; +import timelineData from './data'; +import React from 'react'; + +export default () => ( + <div style={{ width: '100%', height: '500px' }}> + <Chrono items={timelineData} /> + </div> +); + |
+ 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 | + + + + + + + + + + + + + + | import Chrono from '../components/index'; +import timelineData from './data'; +import React from 'react'; + +export default () => ( + <div style={{ width: '100%', height: '750px' }}> + <Chrono + items={timelineData} + mode="VERTICAL" + slideShow + slideItemDuration={4500} + /> + </div> +); + |
+ 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 | + + + + + + + + + | import React from 'react'; +import Chrono from '../components/index'; +import timelineData from './data'; + +export default () => ( + <div style={{ width: '100%', height: '750px' }}> + <Chrono items={timelineData} mode="VERTICAL" /> + </div> +); + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { TimelineItemModel } from './@models/TimelineItemModel'; + +export default [ + { + title: 'May 26, 1951', + cardTitle: 'Origins', + cardSubtitle: + 'President John F. Kennedy says he will send astronauts to the moon by 1970.', + }, + { + title: 'Jan. 26, 1962', + cardTitle: 'Ranger crashes into moon', + cardSubtitle: 'Unmanned Ranger sent to crash into moon but misses target.', + }, + { + title: 'Nov. 6, 1966', + cardTitle: '5 satellites map moon', + cardSubtitle: 'Second of five U.S. satellites sent to orbit, map moon.', + }, + { + title: 'Jan. 27, 1967', + cardTitle: 'First soft landing', + cardSubtitle: + 'Moon probe makes soft landing near crater Tycho. Photos, soil samples taken.', + }, + { + title: 'Oct. 11, 1968', + cardTitle: 'Manned flight', + cardSubtitle: 'First manned flight of Apollo 7 spacecraft', + }, + { + title: 'Dec. 21, 1968', + cardTitle: 'Saturn rockets', + cardSubtitle: + 'First flight to the moon carried by Saturn V rocket. Apollo 8 astronauts orbit the moon 10 times', + }, + { + title: 'March 3, 1969', + cardTitle: 'Lunar module', + cardSubtitle: 'First flight of lunar module', + }, + { + title: 'May 18, 1969', + cardTitle: 'Apollo 10', + cardSubtitle: 'Apollo 10 flies around moon', + }, + { + title: 'July 16, 1969', + cardTitle: 'Astronauts', + cardSubtitle: + 'Astronauts Neil Armstrong, Buzz Aldrin and Michael Collins fly to moon on Apollo 11', + }, + { + title: 'July 20, 1969', + cardTitle: 'Neil Armstrong', + cardSubtitle: 'Neil Armstrong becomes the first man to walk on the moon', + }, + { + title: 'Nov. 14, 1969', + cardTitle: 'Second landing', + cardSubtitle: 'Apollo 12 astronauts make second landing', + }, + { + title: 'Feb. 19, 1970', + cardTitle: 'Failure', + cardSubtitle: 'U.S.S.R. fails fourth attempt to launch a moon rocket', + }, + { + title: 'April 11, 1970', + cardTitle: 'Mission 13', + cardSubtitle: 'Apollo 13 moon mission aborted when oxygen tank explodes', + }, + { + title: 'Jan. 31, 1971', + cardTitle: 'Mission 14', + cardSubtitle: 'Apollo 14 lands on the moon', + }, + { + title: 'May 18, 1969', + cardTitle: 'Mission 15', + cardSubtitle: + 'Apollo 15 makes fourth landing. First use of the lunar rover vehicle', + }, + { + title: 'May 18, 1969', + cardTitle: 'Mission 16', + cardSubtitle: 'Apollo 16 makes fifth landing', + }, + { + title: 'May 18, 1969', + cardTitle: 'Mission 17', + cardSubtitle: 'Apollo 17 makes final moon landing', + }, +] as TimelineItemModel[]; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
basic-horizontal.tsx | +
+
+ |
+ 0% | +0/9 | +0% | +0/1 | +0% | +0/1 | +0% | +0/9 | +
basic-slideshow.tsx | +
+
+ |
+ 0% | +0/14 | +0% | +0/1 | +0% | +0/1 | +0% | +0/14 | +
basic-vertical.tsx | +
+
+ |
+ 0% | +0/9 | +0% | +0/1 | +0% | +0/1 | +0% | +0/9 | +
data.ts | +
+
+ |
+ 0% | +0/94 | +0% | +0/1 | +0% | +0/1 | +0% | +0/94 | +
+ 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 | +
+
+ |
+ 0% | +0/8 | +0% | +0/1 | +0% | +0/1 | +0% | +0/8 | +
react-chrono.ts | +
+
+ |
+ 0% | +0/5 | +0% | +0/1 | +0% | +0/1 | +0% | +0/5 | +
+ 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 | + + + + + + + + | import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './demo/App'; +import './index.css'; + +const Root = ReactDOM.createRoot(document.getElementById('root')); + +Root.render(<App />); + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | export interface Theme { + // card background color + cardBgColor?: string; + + // card details background color + cardDetailsBackGround?: string; + + // card details color + cardDetailsColor?: string; + + cardMediaBgColor?: string; + + // card subtitle color + cardSubtitleColor?: string; + + // card title color + cardTitleColor?: string; + + // details color + detailsColor?: string; + + // icon background color + iconBackgroundColor?: string; + + // nested card background color + nestedCardBgColor?: string; + + nestedCardDetailsBackGround?: string; + + // nested card details color + nestedCardDetailsColor?: string; + + // nested card subtitle color + nestedCardSubtitleColor?: string; + + // nested card title color + nestedCardTitleColor?: string; + + // primary color + primary?: string; + + // secondary color + secondary?: string; + + // text color + textColor?: string; + + // title color + titleColor?: string; + + // title color for active tabs + titleColorActive?: string; +} + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + | import { Theme } from './Theme'; + +/** + * Represents the model for a title element. + */ +export interface TitleModel { + // Indicates if the title is active. + active?: boolean; + + // Alignment of the title (left or right). + align?: 'left' | 'right'; + + // Additional CSS class string for styling. + classString?: string; + + // Theme to be applied to the title element. + theme?: Theme; + + // Text content of the title. + title?: string; +} + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { ReactNode } from 'react'; +import { Theme } from './Theme'; +import { TimelineItemModel } from './TimelineItemModel'; +import { Media } from './TimelineMediaModel'; + +/** + * Represents the model for timeline content. + */ +export type TimelineContentModel = { + // Indicates if the content is active. + active?: boolean; + + // Directory for branch-related content. + branchDir?: string; + + // Main content of the timeline item. + content?: string | ReactNode; + + // Custom content for the timeline item. + customContent?: React.ReactNode; + + // Detailed text for the timeline item. + detailedText?: string | string[]; + + // Indicates if the timeline item should be flipped. + flip?: boolean; + + // Indicates if the timeline item has focus. + hasFocus?: boolean; + + // Unique identifier for the timeline item. + id?: string; + + // Indicates if the timeline item is nested. + isNested?: boolean; + + // Array of timeline items nested within this item. + items?: TimelineItemModel[]; + + // Media associated with the timeline item. + media?: Media; + + // Height of nested card within the item. + nestedCardHeight?: number; + + // Click event handler for the timeline item. + onClick?: (id: string) => void; + + // Elapsed event handler for the timeline item. + onElapsed?: (id?: string) => void; + + // Show more event handler for the timeline item. + onShowMore: () => void; + + // Indicates if slide show is active. + slideShowActive?: boolean; + + // Theme to be applied to the timeline item. + theme?: Theme; + + // Custom content for the entire timeline content. + timelineContent?: React.ReactNode; + + // Title of the timeline item. + title?: string; + + // URL associated with the timeline item. + url?: string; +}; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | /** + * Represents the model for controlling the timeline. + */ +export interface TimelineControlModel { + // Index of the active timeline item. + activeTimelineItem?: number; + + // Indicates whether the left control is disabled. + disableLeft: boolean; + + // Indicates whether the right control is disabled. + disableRight: boolean; + + // Unique identifier for the control. + id?: string; + + // Indicates whether the dark mode is enabled. + isDark?: boolean; + + // Click event handler for moving to the first item. + onFirst: () => void; + + // Click event handler for moving to the last item. + onLast: () => void; + + // Click event handler for moving to the next item. + onNext: () => void; + + // Click event handler for pausing the slide show. + onPaused?: () => void; + + // Click event handler for moving to the previous item. + onPrevious: () => void; + + // Click event handler for replaying the slide show. + onReplay?: () => void; + + // Click event handler for toggling dark mode. + onToggleDarkMode?: () => void; + + // Indicates whether slide show mode is enabled. + slideShowEnabled?: boolean; + + // Indicates whether the slide show is currently running. + slideShowRunning?: boolean; +} + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { ReactNode } from 'react'; +// import { Theme } from './Theme'; +import { TimelineCardModel } from './TimelineItemModel'; +import { TimelineMode } from './TimelineModel'; + +/** + * Represents the model for a horizontal timeline. + */ +export interface TimelineHorizontalModel { + // Function to trigger auto-scrolling. + autoScroll: (t: Partial<Scroll>) => void; + + // Children elements for the content details. + contentDetailsChildren?: ReactNode | ReactNode[]; + + // Click event handler for timeline item click. + handleItemClick: (id?: string) => void; + + // Indicates if the timeline has focus. + hasFocus?: boolean; + + // Children elements for icons. + iconChildren?: ReactNode; + + // Indicates if the timeline is nested. + isNested?: boolean; + + // Width of each timeline item. + itemWidth?: number; + + // Array of timeline card models. + items: TimelineCardModel[]; + + // Mode of the timeline (horizontal or vertical). + mode?: TimelineMode; + + // Height of nested card within the timeline item. + nestedCardHeight?: number; + + // Elapsed event handler for timeline items. + onElapsed?: (id?: string) => void; + + // Indicates if the slide show is running. + slideShowRunning?: boolean; + + // Unique identifier for the timeline wrapper. + wrapperId: string; +} + +export interface Scroll { + /** + * Height of the Timeline card content + * + * @type {number} + * @memberof Scroll + */ + contentHeight: number; + + /** + * Offset of the Content card + * + * @type {number} + * @memberof Scroll + */ + contentOffset: number; + + /** + * Offset of the timeline point + * + * @type {number} + * @memberof Scroll + */ + pointOffset: number; + + /** + * Width of the timeline point + * + * @type {number} + * @memberof Scroll + */ + pointWidth: number; + + /** + * Height of the timeline point + * + * @type {number} + * @memberof Scroll + */ + timelinePointHeight: number; +} + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { ReactNode } from 'react'; +import { Theme } from './Theme'; +import { Scroll } from './TimelineHorizontalModel'; +import { Media } from './TimelineMediaModel'; +import { TimelineProps } from './TimelineModel'; + +/** + * Represents the model for a timeline item. + */ +export interface TimelineItemModel { + // Internal property for handling date. + _dayjs?: any; + + // Indicates if the timeline item is active. + active?: boolean; + + // Detailed text for the timeline card. + cardDetailedText?: string | string[]; + + // Subtitle for the timeline card. + cardSubtitle?: string; + + // Title for the timeline card. + cardTitle?: string; + + // Main content of the timeline item. + content?: ReactNode | ReactNode[]; + + // Date associated with the timeline item. + date?: number | string | Date; + + // Unique identifier for the timeline item. + id?: string; + + // Indicates if the timeline item is nested. + isNested?: boolean; + + // Array of nested timeline items. + items?: TimelineItemModel[]; + + // Media associated with the timeline item. + media?: Media; + + // Position of the timeline item. + position?: string; + + // Custom content for the timeline content. + timelineContent?: ReactNode; + + // Title of the timeline item. + title?: string; + + // URL associated with the timeline item. + url?: string; + + // Indicates if the timeline item is visible. + visible?: boolean; +} + +/** + * Represents the model for a timeline card. + */ +export type TimelineCardModel = Pick< + TimelineItemModel, + | 'id' + | 'visible' + | 'title' + | 'active' + | 'cardDetailedText' + | 'cardSubtitle' + | 'cardTitle' + | 'media' + | 'url' + | 'timelineContent' + | 'isNested' + | 'items' +> & { + // Function for auto-scrolling. + autoScroll?: ({ + pointOffset, + pointWidth, + timelinePointHeight, + contentHeight, + }: Partial<Scroll>) => void; + + // Custom content for the timeline card. + customContent?: React.ReactNode | React.ReactNode[]; + + // Indicates if the timeline card has focus. + hasFocus?: boolean; + + // Icon element for the timeline card. + iconChild?: React.ReactNode; + + // Click event handler for the timeline card. + onClick?: (id?: string) => void; + + // Elapsed event handler for the timeline card. + onElapsed?: (id?: string) => void; + + // Duration of slide item transitions. + slideItemDuration?: number; + + // Indicates if the slide show is running. + slideShowRunning?: boolean; + + // Theme to be applied to the timeline card. + theme?: Theme; + + // Unique identifier for the timeline wrapper. + wrapperId: string; +} & Pick<TimelineProps, 'cardHeight' | 'cardWidth' | 'nestedCardHeight'>; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { ForwardRefExoticComponent, ReactNode } from 'react'; +import { Theme } from './Theme'; +import { TextOrContentModel } from '../components/timeline-elements/timeline-card-content/text-or-content'; + +/** + * Represents the type of media (video or image). + */ +export type MediaType = 'VIDEO' | 'IMAGE'; + +/** + * Represents the source of a media element. + */ +export interface MediaSource { + // Type of the media source (optional). + type?: string; + + // URL of the media source. + url: string; +} + +/** + * Represents the model for media content. + */ +export interface Media { + // Name of the media (optional). + name?: string; + + // Source of the media. + source: MediaSource; + + // Type of the media (video or image). + type: MediaType; +} + +/** + * Represents the state of media playback. + */ +export interface MediaState { + // Unique identifier for the media. + id?: string; + + // Indicates if media is paused. + paused?: boolean; + + // Indicates if media is currently playing. + playing?: boolean; +} + +/** + * Represents the model for media within a card. + */ +export interface CardMediaModel { + // Indicates if the media is active. + active?: boolean; + + // Height of the card containing the media. + cardHeight?: number; + + // Content associated with the media. + content?: string | ReactNode; + + // Text details associated with the media. + // detailsText?: ForwardRefExoticComponent<TextOrContentModel>; + detailsText?: ForwardRefExoticComponent<TextOrContentModel>; + + // Indicates if media should be hidden. + hideMedia: boolean; + + // Unique identifier for the media. + id?: string; + + // Media content and source information. + media: Media; + + // Event handler for media state changes. + onMediaStateChange: (state: MediaState) => void; + + // Indicates if media playback is paused. + paused?: boolean; + + // Reference to the progress bar element. + progressRef?: React.RefObject<HTMLDivElement>; + + // Interval for remaining media playback. + remainInterval?: number; + + // Indicates if media playback is resuming. + resuming?: boolean; + + // Indicates if the progress bar should be shown. + showProgressBar?: boolean; + + // Indicates if slideshow mode is active. + slideshowActive?: boolean; + + // Initial width for the media element. + startWidth?: number; + + // Theme to be applied to the media card. + theme?: Theme; + + // Title of the media card. + title?: string; + + // Direction of the triangle indicator. + triangleDir?: string; + + // URL associated with the media. + url?: string; +} + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { Theme } from './Theme'; +import { TimelineItemModel } from './TimelineItemModel'; + +/** + * model internally used by the component + * + * @export + * @interface TimelineModel + * @extends {TimelineProps} + */ +export type TimelineModel = Pick< + TimelineProps, + | 'items' + | 'onItemSelected' + | 'onRestartSlideshow' + | 'theme' + | 'slideShow' + | 'onScrollEnd' + | 'mode' + | 'enableOutline' + | 'hideControls' + | 'timelinePointDimension' + | 'nestedCardHeight' + | 'noUniqueId' + | 'uniqueId' +> & { + activeTimelineItem?: number; + contentDetailsChildren?: React.ReactNode | React.ReactNode[]; + iconChildren?: React.ReactNode | React.ReactNode[]; + isChild?: boolean; + onFirst?: () => void; + onLast?: () => void; + onNext?: () => void; + onOutlineSelection?: (index: number) => void; + onPaused?: () => void; + onPrevious?: () => void; + onTimelineUpdated?: (id: number) => void; + slideItemDuration?: number; + slideShowEnabled?: boolean; + slideShowRunning?: boolean; +}; + +/** + * Main props used by the host app. + * + * @export + * @interface TimelineProps + */ +export type TimelineProps = { + // active item index + activeItemIndex?: number; + + // alignMedia?: 'left' | 'right' | 'center'; + + // allow dynamic update of the timeline items + allowDynamicUpdate?: boolean; + + // removes the borders from the cards + borderLessCards?: boolean; + + // custom button texts + buttonTexts?: { + dark?: string; + first: string; + last: string; + light?: string; + next?: string; + play?: string; + previous?: string; + stop?: string; + }; + + // minimum height of the card + cardHeight?: number; + + // remove/hide cards + cardLess?: boolean; + + // position of the card in horizontal mode + cardPositionHorizontal?: 'TOP' | 'BOTTOM'; + + // minimum width of the card + cardWidth?: number; + + children?: React.ReactElement | React.ReactElement[]; + + // custom class names for the different elements + classNames?: { + card?: string; + cardMedia?: string; + cardSubTitle?: string; + cardText?: string; + cardTitle?: string; + controls?: string; + title?: string; + }; + + // height of the details text + contentDetailsHeight?: number; + + darkMode?: boolean; + // disables the auto scroll on click + disableAutoScrollOnClick?: boolean; + + // disables the click on the circle + disableClickOnCircle?: boolean; + + disableInteraction?: boolean; + + disableNavOnKey?: boolean; + + disableTimelinePoint?: boolean; + + enableBreakPoint?: boolean; + + enableDarkToggle?: boolean; + + // enables the outline view + enableOutline?: boolean; + + // flips the layout, useful for RTL + flipLayout?: boolean; + + // focus the active item on load + focusActiveItemOnLoad?: boolean; + + // custom font sizes + fontSizes?: { + cardSubtitle?: string; + cardText?: string; + cardTitle?: string; + title?: string; + }; + + // hides the ui controls + hideControls?: boolean; + + highlightCardsOnHover?: boolean; + + itemWidth?: number; + + // collection of timeline items + items?: TimelineItemModel[]; + + // width of the timeline line + lineWidth?: number; + + // minimum height of the media element + mediaHeight?: number; + + mediaSettings?: { + align?: 'left' | 'right' | 'center'; + imageFit?: 'cover' | 'contain' | 'fill' | 'none'; + }; + + // mode of the timeline. VERTICAL, HORIZONTAL, VERTICAL_ALTERNATING + mode?: TimelineMode; + + nestedCardHeight?: number; + + noUniqueId?: boolean; + + // callback when an item is selected + onItemSelected?: ( + data: Pick< + TimelineItemModel, + 'title' | 'cardDetailedText' | 'cardSubtitle' | 'cardTitle' + > & { index: number }, + ) => void; + + // callback when the slideshow is restarted + onRestartSlideshow?: () => void; + + // callback when the scroll ends + onScrollEnd?: () => void; + + onThemeChange?: () => void; + + parseDetailsTextHTML?: boolean; + + // option to enable scrollbar + scrollable?: boolean | { scrollbar: boolean }; + + // show all cards in horizontal mode + showAllCardsHorizontal?: boolean; + + showProgressOnSlideshow?: boolean; + + // duration each slide is shown + slideItemDuration?: number; + + // enables the slideshow + slideShow?: boolean; + + slideShowType?: SlideShowType; + + textOverlay?: boolean; + + // custom theme + theme?: Theme; + + // width of the timeline circle + timelinePointDimension?: number; + + timelinePointShape?: 'circle' | 'square' | 'diamond'; + + // title for the timeline + title?: string; + + titleDateFormat?: string; + + uniqueId?: string; + + // enables the read more button + useReadMore?: boolean; + + verticalBreakPoint?: number; +}; + +export type SlideShowType = 'reveal' | 'slide_in' | 'slide_from_sides'; + +export type TimelineMode = 'VERTICAL' | 'HORIZONTAL' | 'VERTICAL_ALTERNATING'; + |
+ 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 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { Theme } from './Theme'; +import { Scroll } from './TimelineHorizontalModel'; +import { TimelineCardModel, TimelineItemModel } from './TimelineItemModel'; +import { Media } from './TimelineMediaModel'; +import { TimelineProps } from './TimelineModel'; + +/** + * Represents the props for the timeline component. + */ +export type Props = Pick< + TimelineProps, + // List of properties inherited from TimelineProps: + | 'flipLayout' + | 'theme' + | 'mode' + | 'timelinePointDimension' + | 'lineWidth' + | 'cardHeight' + | 'enableOutline' + | 'disableClickOnCircle' + | 'cardLess' + | 'nestedCardHeight' +> & { + // Indicates whether to show alternate cards. + alternateCards?: boolean; + + // Indicates if the timeline has focus. + hasFocus?: boolean; + + // Click event handler for timeline item click. + onClick: (id?: string) => void; + + // Elapsed event handler for timeline items. + onElapsed?: (id?: string) => void; + + // Duration for slide item transitions. + slideItemDuration?: number; + + // Indicates if the slide show is running. + slideShowRunning?: boolean; + + // Theme to be applied to the timeline component. + theme?: Theme; +}; + +type VerticalModel = Pick< + Props, + | 'alternateCards' + | 'hasFocus' + | 'onClick' + | 'onElapsed' + | 'slideShowRunning' + | 'mode' + | 'timelinePointDimension' + | 'lineWidth' + | 'disableClickOnCircle' + | 'cardLess' + | 'nestedCardHeight' +> & + Pick< + TimelineItemModel, + | 'cardDetailedText' + | 'cardSubtitle' + | 'cardTitle' + | 'title' + | 'url' + | 'timelineContent' + | 'items' + | 'isNested' + > & { active?: boolean; className: string; id?: string }; + +/** + * Represents the model for a vertical timeline point. + */ +export type TimelinePointModel = Omit<VerticalModel, 'timelineContent'> & { + // Icon element associated with the timeline point. + iconChild?: React.ReactNode; + + // Event handler for activating the timeline point. + onActive: (pointOffset: number) => void; +}; + +/** + * Represents the model for a vertical timeline item. + */ +export interface VerticalItemModel extends VerticalModel { + // Children elements for content details. + contentDetailsChildren?: React.ReactNode; + + // Icon element associated with the timeline item. + iconChild?: React.ReactNode; + + // Index of the timeline item. + index: number; + + // Nested timeline items. + items?: TimelineItemModel[]; + + // Media content associated with the timeline item. + media?: Media; + + // Event handler for activating the timeline item. + onActive: ( + pointOffset: number, + contentHeight: number, + contentOffset: number, + ) => void; + + // Event handler for showing more content. + onShowMore?: () => void; + + // Indicates if the timeline item is visible. + visible?: boolean; +} + +/** + * Represents the model for a vertical timeline. + */ +export type TimelineVerticalModel = Pick< + Props, + // List of properties inherited from Props: + | 'alternateCards' + | 'enableOutline' + | 'mode' + | 'onClick' + | 'onElapsed' + | 'slideShowRunning' + | 'theme' + | 'hasFocus' + | 'cardLess' + | 'nestedCardHeight' +> & { + // Index of the active timeline item. + activeTimelineItem?: number; + + // Function for auto-scrolling the timeline. + autoScroll: (s: Partial<Scroll>) => void; + + // Children nodes for the timeline. + childrenNode?: React.ReactNode; + + // Children elements for content details. + contentDetailsChildren?: React.ReactNode; + + // Children elements for icons. + iconChildren?: React.ReactNode; + + // Array of timeline card models. + items: TimelineCardModel[]; + + // Event handler for selecting an outline item. + onOutlineSelection?: (index: number) => void; +}; + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
Theme.ts | +
+
+ |
+ 0% | +0/53 | +0% | +0/1 | +0% | +0/1 | +0% | +0/53 | +
TimelineCardTitleModel.ts | +
+
+ |
+ 0% | +0/21 | +0% | +0/1 | +0% | +0/1 | +0% | +0/21 | +
TimelineContentModel.ts | +
+
+ |
+ 0% | +0/69 | +0% | +0/1 | +0% | +0/1 | +0% | +0/69 | +
TimelineControlModel.ts | +
+
+ |
+ 0% | +0/46 | +0% | +0/1 | +0% | +0/1 | +0% | +0/46 | +
TimelineHorizontalModel.ts | +
+
+ |
+ 0% | +0/90 | +0% | +0/1 | +0% | +0/1 | +0% | +0/90 | +
TimelineItemModel.ts | +
+
+ |
+ 0% | +0/112 | +0% | +0/1 | +0% | +0/1 | +0% | +0/112 | +
TimelineMediaModel.ts | +
+
+ |
+ 0% | +0/110 | +0% | +0/1 | +0% | +0/1 | +0% | +0/110 | +
TimelineModel.ts | +
+
+ |
+ 0% | +0/222 | +0% | +0/1 | +0% | +0/1 | +0% | +0/222 | +
TimelineVerticalModel.ts | +
+
+ |
+ 0% | +0/153 | +0% | +0/1 | +0% | +0/1 | +0% | +0/153 | +
+ 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 | + + + + + | import ReactChrono from './components'; +import { TimelineItemModel } from './models/TimelineItemModel'; + +export { ReactChrono as Chrono }; +export type TimelineItem = TimelineItemModel; + |
+ 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 | +
+
+ |
+ 91.13% | +72/79 | +100% | +14/14 | +75% | +6/8 | +91.13% | +72/79 | +
+ 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 | 1x +1x +1x +1x +1x +1x +1x +1x +1x +7x +7x +1x +1x +1x +1x +33x +33x +33x +33x +33x +33x +1x +1x +3x +1x +1x +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 +1x +1x +4x +4x +4x +1x +1x +4x +1x +1x +2x +4x +1x +1x +1x +1x +1x +1x +1x + + +1x +1x + + + + + + | import { SlideShowType, TimelineMode } from '@models/TimelineModel'; +import { darkTheme, defaultTheme } from '../components/common/themes'; +import santizeHtml from 'sanitize-html'; + +export const uniqueID = () => { + const chars = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let autoId = ''; + for (let i = 0; i < 7; i++) { + autoId += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return autoId; +}; + +export const hexToRGBA = (hex: string, alpha: number): string => { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +}; + +export const getDefaultThemeOrDark = (isDark?: boolean) => { + if (isDark) { + return darkTheme; + } + return defaultTheme; +}; + +export const getDefaultClassNames = () => ({ + card: 'rc-card', + cardMedia: 'rc-card-media', + cardSubTitle: 'rc-card-subtitle', + cardText: 'rc-card-text', + cardTitle: 'rc-card-title', + controls: 'rc-controls', + title: 'rc-title', +}); + +export const getDefaultButtonTexts = () => ({ + dark: 'Switch to Dark Mode', + first: 'Go to First', + last: 'Go to Last', + light: 'Switch to Light Mode', + next: 'Next', + play: 'Play Slideshow', + previous: 'Previous', + stop: 'Stop Slideshow', +}); + +//get slidehow type based on mode + +export const getSlideShowType: (mode: TimelineMode) => SlideShowType = ( + mode, +) => { + if (mode === 'HORIZONTAL') { + return 'reveal'; + } + if (mode === 'VERTICAL') { + return 'reveal'; + } + + if (mode === 'VERTICAL_ALTERNATING') { + return 'slide_from_sides'; + } + + return 'reveal'; +}; + +export const isTextArray = (text: string | string[]): text is string[] => { + return Array.isArray(text); +}; + +export const sanitizeHtmlText = (text: string | string[]) => { + if (isTextArray(text)) { + return text.map((t) => santizeHtml(t)); + } + return santizeHtml(text); +}; + |
+ 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 | + + + + + + + + | /** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./src/**/*.{jsx,tsx,ts,js}'], + plugins: [], + theme: { + extend: {}, + }, +}; + |