From e22a6c9a97ea15884013103ab98fb1e97b9e4ffd Mon Sep 17 00:00:00 2001 From: Alvaro Leal Date: Thu, 24 Nov 2022 10:10:50 +0100 Subject: [PATCH 01/22] Update pop-up --- .../scripts/react-components/popup/popup-component.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/scripts/react-components/popup/popup-component.jsx b/frontend/scripts/react-components/popup/popup-component.jsx index ec294994f3..ad6e921a32 100644 --- a/frontend/scripts/react-components/popup/popup-component.jsx +++ b/frontend/scripts/react-components/popup/popup-component.jsx @@ -20,7 +20,8 @@ function PopUp({ handleOnRequestClose }) {
- The term Deforestation Risk changes to Deforestation exposure + The term {`"`}Deforestation Risk{`"`} has been replaced with {`"`}Deforestation exposure + {`"`} On 10 November 2022, the term{' '} @@ -34,6 +35,10 @@ function PopUp({ handleOnRequestClose }) { as a measure of the exposure of supply chain actors to deforestation from commodity production based on sourcing patterns. + + Improvements in how commodity deforestation exposure is calculated were made on 7 + December 2022. Numbers accessed before that date will differ from those accessed after. + For more information, see{' '} Date: Thu, 24 Nov 2022 10:48:08 +0100 Subject: [PATCH 02/22] Add methods and data modal disclaimer --- .../versioning-modal.component.jsx | 28 ++++++++++++++++++- .../versioning-modal/versioning-modal.scss | 19 +++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.component.jsx b/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.component.jsx index 4a780d3ebc..2751268a1a 100644 --- a/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.component.jsx +++ b/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.component.jsx @@ -8,7 +8,32 @@ import capitalize from 'lodash/capitalize'; function VersioningModal({ data, context }) { const { url, version } = data || {}; const { countryName, commodityName } = context || {}; - + const renderDisclaimer = () => ( +
+ + + + + Attention needed + + + Please note there are changes in methods before and after 2018 that are relevant for data + interpretation.{' '} + + Check the method document + {' '} + for more information + +
+ ); return (
@@ -71,6 +96,7 @@ function VersioningModal({ data, context }) { + {countryName === 'BRAZIL' && commodityName === 'SOY' && renderDisclaimer()}
diff --git a/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.scss b/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.scss index a5ba7de16c..70c12dac12 100644 --- a/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.scss +++ b/frontend/scripts/react-components/tool/tool-modal/versioning-modal/versioning-modal.scss @@ -24,4 +24,23 @@ border-top: 1px solid $medium-gray; margin: 30px 0 15px; } + + .disclaimer { + margin-top: 30px; + } + + .disclaimer-icon { + margin: 0 5px 0 0; + } + + .disclaimer-title { + display: flex; + margin-bottom: 10px; + } + + .pink-link { + color: $strong-pink; + text-decoration: underline; + font-weight: $font-weight-bold; + } } From ba14c3301762c4e315a9f9a9776647d2c8c8048c Mon Sep 17 00:00:00 2001 From: Alvaro Leal Date: Thu, 24 Nov 2022 12:26:55 +0100 Subject: [PATCH 03/22] Add brazil soy tool disclaimer --- .../methods-disclaimer-banner.component.jsx | 43 ++++++++++++++++++ .../methods-disclaimer-banner.jsx | 34 ++++++++++++++ .../methods-disclaimer-banner.scss | 44 +++++++++++++++++++ .../tool/map/map.selectors.js | 4 ++ .../react-components/tool/tool.component.jsx | 25 +++++++++-- .../scripts/react-components/tool/tool.js | 8 +++- 6 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx create mode 100644 frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.jsx create mode 100644 frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss diff --git a/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx new file mode 100644 index 0000000000..2ebd4edb33 --- /dev/null +++ b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import Button from 'react-components/shared/button'; +import Icon from 'react-components/shared/icon'; +import Text from 'react-components/shared/text'; +import 'react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss'; + +function MethodsDisclaimerBanner({ setAccepted, accepted }) { + return accepted ? null : ( +
+
+ + Please note there are changes in methods before and after 2018 that are relevant for data + interpretation. + + + + Check the method document + {' '} + for more information + +
+ +
+ ); +} + +MethodsDisclaimerBanner.propTypes = { + setAccepted: PropTypes.func.isRequired, + accepted: PropTypes.bool +}; + +export default MethodsDisclaimerBanner; diff --git a/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.jsx b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.jsx new file mode 100644 index 0000000000..e7ec382bfc --- /dev/null +++ b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.jsx @@ -0,0 +1,34 @@ +import React, { useState, useEffect } from 'react'; +import MethodsBannerComponent from 'react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component'; + +const acceptedMethodsBanner = { + key: 'TRASE_EARTH_ACCEPTED_METHODS_DISCLAIMER', + get() { + return localStorage.getItem(this.key); + }, + set(date) { + return localStorage.setItem(this.key, date); + } +}; + +const initAccepted = () => { + const value = acceptedMethodsBanner.get(); + if (value) { + return typeof JSON.parse(value) === 'number'; + } + return false; +}; + +const MethodsBannerContainer = () => { + const [accepted, setAccepted] = useState(initAccepted); + + useEffect(() => { + if (!acceptedMethodsBanner.get() && accepted === true) { + acceptedMethodsBanner.set(Date.now()); + } + }, [accepted]); + + return setAccepted(true)} accepted={accepted} />; +}; + +export default MethodsBannerContainer; diff --git a/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss new file mode 100644 index 0000000000..fe25df328e --- /dev/null +++ b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss @@ -0,0 +1,44 @@ +@import 'styles/settings'; + +.c-methods-disclaimer { + position: fixed; + left: 0; + right: 0; + bottom: 0; + transform: translateY(100%); + background-color: $green-200; + + padding: 20px; + + z-index: $z-above-veil; + box-shadow: $box-shadow; + animation: appear 300ms 2s forwards; + + @keyframes appear { + 0% { transform: translateY(100%) } + 100% { transform: translateY(0) } + } + + .disclaimer-content { + display: flex; + flex-direction: column; + text-align: center; + } + + .close-button { + position: absolute; + right: 30px; + top: 40%; + border: none; + } + + .icon-close { + width: 20px; + height: 20px; + } + + .link { + text-decoration: underline; + font-weight: $font-weight-bold; + } +} diff --git a/frontend/scripts/react-components/tool/map/map.selectors.js b/frontend/scripts/react-components/tool/map/map.selectors.js index fbe2e3c12d..0eb9fe1e15 100644 --- a/frontend/scripts/react-components/tool/map/map.selectors.js +++ b/frontend/scripts/react-components/tool/map/map.selectors.js @@ -17,6 +17,10 @@ export const getCountryName = createSelector( [getSelectedContext], selectedContext => selectedContext?.countryName || null ); +export const getCommodityName = createSelector( + [getSelectedContext], + selectedContext => selectedContext?.commodityName || null +); const getNodeAttributes = state => state.toolLinks.data.nodeAttributes || null; const getHighlightedNodesCoordinates = state => state.toolLayers.highlightedNodeCoordinates; diff --git a/frontend/scripts/react-components/tool/tool.component.jsx b/frontend/scripts/react-components/tool/tool.component.jsx index 4c7733ef97..8100fd324d 100644 --- a/frontend/scripts/react-components/tool/tool.component.jsx +++ b/frontend/scripts/react-components/tool/tool.component.jsx @@ -1,6 +1,8 @@ import React, { useEffect, useMemo, Suspense } from 'react'; import PropTypes from 'prop-types'; import EventManager from 'utils/eventManager'; +import MethodsDisclaimerBanner from 'react-components/shared/methods-disclaimer-banner'; + import ColumnsSelectorGroupContainer from 'react-components/tool/columns-selector-group/columns-selector-group.container'; import SplittedView from 'react-components/tool/splitted-view'; import MapBoxMap from 'react-components/tool/map/map'; @@ -57,11 +59,15 @@ const Tool = props => { urlPropHandlers, mapSidebarOpen, noLinksFound, - activeModal + activeModal, + countryName, + commodityName } = props; - const { width } = useWindowSize(); - + const isBrazilSoyException = useMemo(() => countryName === 'BRAZIL' && commodityName === 'SOY', [ + commodityName, + countryName + ]); useEffect(() => { evManager.addEventListener(window, 'resize', resizeSankeyTool); const body = document.querySelector('body'); @@ -95,13 +101,22 @@ const Tool = props => { showBackground={section === 'data-view'} selectYears={selectYears} /> + {isBrazilSoyException && }
), - [noLinksFound, mapSidebarOpen, section, toolYearProps, selectYears, activeModal] + [ + noLinksFound, + mapSidebarOpen, + section, + toolYearProps, + selectYears, + activeModal, + isBrazilSoyException + ] ); if (width <= BREAKPOINTS.tablet) { @@ -125,6 +140,8 @@ Tool.propTypes = { noLinksFound: PropTypes.bool, activeModal: PropTypes.string, section: PropTypes.string, + countryName: PropTypes.string, + commodityName: PropTypes.string, toolYearProps: PropTypes.shape({ years: PropTypes.array, selectedYears: PropTypes.array diff --git a/frontend/scripts/react-components/tool/tool.js b/frontend/scripts/react-components/tool/tool.js index b3086d9863..d69beb9a4c 100644 --- a/frontend/scripts/react-components/tool/tool.js +++ b/frontend/scripts/react-components/tool/tool.js @@ -13,6 +13,10 @@ import { getNodesPanelUrlProps } from 'react-components/nodes-panel/nodes-panel. import toolLayerSerializer from 'react-components/tool-layers/tool-layers.serializers'; import toolLinksSerializer from 'react-components/tool-links/tool-links.serializers'; import nodesPanelSerializer from 'react-components/nodes-panel/nodes-panel.serializers'; +import { + getCountryName, + getCommodityName +} from 'react-components/tool/map/map.selectors'; const { urlPropHandlers: toolLayersUrlPropHandlers } = toolLayerSerializer; const { urlPropHandlers: toolLinksUrlPropHandlers } = toolLinksSerializer; @@ -36,7 +40,9 @@ const mapStateToProps = state => ({ section: state.location.payload.section, mapSidebarOpen: state.app.isMapLayerVisible, noLinksFound: state.toolLinks.noLinksFound, - activeModal: state.toolLayers.activeModal + activeModal: state.toolLayers.activeModal, + countryName: getCountryName(state), + commodityName: getCommodityName(state), }); const mapDispatchToProps = { From 5ef5d1b3366c3dd989052715bba9723aefad28b6 Mon Sep 17 00:00:00 2001 From: Alvaro Leal Date: Thu, 24 Nov 2022 15:50:51 +0100 Subject: [PATCH 04/22] Extract disclaimer and add it to downloads --- .../data-portal-form.component.jsx | 6 +++- .../data-portal/data-portal.component.jsx | 9 +++-- .../methods-disclaimer-component.jsx | 35 +++++++++++++++++++ .../methods-disclaimer/methods-disclaimer.js | 1 + .../methods-disclaimer.scss | 21 +++++++++++ .../versioning-modal.component.jsx | 30 ++-------------- .../versioning-modal/versioning-modal.scss | 19 ---------- 7 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 frontend/scripts/react-components/shared/methods-disclaimer/methods-disclaimer-component.jsx create mode 100644 frontend/scripts/react-components/shared/methods-disclaimer/methods-disclaimer.js create mode 100644 frontend/scripts/react-components/shared/methods-disclaimer/methods-disclaimer.scss diff --git a/frontend/scripts/react-components/data-portal/data-portal-form/data-portal-form.component.jsx b/frontend/scripts/react-components/data-portal/data-portal-form/data-portal-form.component.jsx index 8b99fcac03..f2c16a12d3 100644 --- a/frontend/scripts/react-components/data-portal/data-portal-form/data-portal-form.component.jsx +++ b/frontend/scripts/react-components/data-portal/data-portal-form/data-portal-form.component.jsx @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import axios from 'axios'; +import MethodsDisclaimer from 'react-components/shared/methods-disclaimer'; import { COUNTRIES } from '../../../countries'; import './download-form.scss'; @@ -99,6 +100,7 @@ class DataPortalForm extends Component { } render() { + const { isBrazilSoyException } = this.props; return (
@@ -114,6 +116,7 @@ class DataPortalForm extends Component { We'd love to hear about how you use the data and how we could improve it. Please fill in details below and click on 'submit':

+ {isBrazilSoyException && }
); - case 'sentence': + case 'sentence': { + const hasDisclaimer = + title === 'Selection overview' && + chartConfig.dashboardMeta.context.commodityName === 'SOY' && + chartConfig.dashboardMeta.context.countryName === 'BRAZIL' && + meta.yAxis.label === 'Trade volume'; + return (
+ {hasDisclaimer && ( + + )}
); + } case 'ranking': return (
@@ -138,10 +152,7 @@ function DashboardWidget(props) { containerRef={widgetBoxRef} /> {chartConfig.xAxisLabel && ( - + )} ); diff --git a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.scss b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.scss index f0701f2b1a..6eb155324d 100644 --- a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.scss +++ b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.scss @@ -17,6 +17,16 @@ $this: 'c-dashboard-widget'; justify-content: center; height: 100%; padding: 0 30px; + + .info-tooltip { + margin-bottom: 20px; + + .tooltip-react-icon { + opacity: 1; + width: 20px; + height: 20px; + } + } } .widget-centered { @@ -136,5 +146,3 @@ $this: 'c-dashboard-widget'; } } } - - diff --git a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx index a2b7ca357c..38109d0344 100644 --- a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx +++ b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx @@ -9,7 +9,11 @@ function DynamicSentenceWidget({ dynamicSentenceParts, variant }) { light: 'grey' }[variant]; if (dynamicSentenceParts) { - return ; + return ( + <> + + + ); } return No data available; } diff --git a/frontend/scripts/react-components/shared/help-tooltip/help-tooltip.component.jsx b/frontend/scripts/react-components/shared/help-tooltip/help-tooltip.component.jsx index 43906bd4b8..a77583af2d 100644 --- a/frontend/scripts/react-components/shared/help-tooltip/help-tooltip.component.jsx +++ b/frontend/scripts/react-components/shared/help-tooltip/help-tooltip.component.jsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-danger */ import React from 'react'; import PropTypes from 'prop-types'; +import cx from 'classnames'; import Tippy from '@tippy.js/react'; import 'tippy.js/dist/tippy.css'; @@ -29,12 +30,13 @@ function HelpTooltipComponent(props) { referenceComponent: ReferenceComponent, position, text, - interactive + interactive, + className } = props; const parsedText = interactive ?
: text; return ( - + Date: Mon, 28 Nov 2022 10:10:11 +0100 Subject: [PATCH 10/22] Put disclaimer next to the sentence --- .../dashboard-widget.component.jsx | 20 +++++++++++-------- .../dynamic-sentence-widget.component.jsx | 7 ++++--- .../tags-group/tags-group.component.jsx | 4 +++- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.component.jsx b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.component.jsx index 6a038694d9..70b1bb6a75 100644 --- a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.component.jsx +++ b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dashboard-widget.component.jsx @@ -113,16 +113,20 @@ function DashboardWidget(props) { chartConfig.dashboardMeta.context.commodityName === 'SOY' && chartConfig.dashboardMeta.context.countryName === 'BRAZIL' && meta.yAxis.label === 'Trade volume'; - + const disclaimer = hasDisclaimer && ( + + ); return (
- - {hasDisclaimer && ( - - )} +
); } diff --git a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx index 38109d0344..285e140172 100644 --- a/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx +++ b/frontend/scripts/react-components/dashboard-element/dashboard-widget/dynamic-sentence-widget/dynamic-sentence-widget.component.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import TagsGroup from 'react-components/shared/tags-group'; import Text from 'react-components/shared/text'; -function DynamicSentenceWidget({ dynamicSentenceParts, variant }) { +function DynamicSentenceWidget({ dynamicSentenceParts, variant, disclaimer }) { const color = { dark: 'white', light: 'grey' @@ -11,7 +11,7 @@ function DynamicSentenceWidget({ dynamicSentenceParts, variant }) { if (dynamicSentenceParts) { return ( <> - + ); } @@ -20,7 +20,8 @@ function DynamicSentenceWidget({ dynamicSentenceParts, variant }) { DynamicSentenceWidget.propTypes = { variant: PropTypes.string, - dynamicSentenceParts: PropTypes.array + dynamicSentenceParts: PropTypes.array, + disclaimer: PropTypes.node }; export default DynamicSentenceWidget; diff --git a/frontend/scripts/react-components/shared/tags-group/tags-group.component.jsx b/frontend/scripts/react-components/shared/tags-group/tags-group.component.jsx index 5c2118970a..0c20973378 100644 --- a/frontend/scripts/react-components/shared/tags-group/tags-group.component.jsx +++ b/frontend/scripts/react-components/shared/tags-group/tags-group.component.jsx @@ -9,7 +9,7 @@ import './tags-group.scss'; import './tags-group-tooltip.variant.scss'; function TagsGroup(props) { - const { as, variant, tags, color, showDropdown, textAs, size } = props; + const { as, variant, tags, color, showDropdown, textAs, size, disclaimer } = props; const Component = textAs || Heading; return React.createElement( @@ -30,6 +30,7 @@ function TagsGroup(props) { {part.value && } ))} + {disclaimer} ); } @@ -39,6 +40,7 @@ TagsGroup.propTypes = { color: PropTypes.string, size: PropTypes.string, showDropdown: PropTypes.bool, + disclaimer: PropTypes.node, tags: PropTypes.array.isRequired, as: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), textAs: PropTypes.oneOfType([PropTypes.string, PropTypes.element, PropTypes.func]) From ee6a9887d4ff10a2755584c285d657e4264076b2 Mon Sep 17 00:00:00 2001 From: Alvaro Leal Date: Mon, 28 Nov 2022 17:04:25 +0100 Subject: [PATCH 11/22] Update methods document --- .../methods-disclaimer-banner.component.jsx | 4 ++-- .../methods-disclaimer/methods-disclaimer-component.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx index 8b03d629bf..64be6b00a2 100644 --- a/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx +++ b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.component.jsx @@ -15,8 +15,8 @@ function MethodsDisclaimerBanner({ setAccepted, accepted }) { Date: Tue, 29 Nov 2022 09:45:46 +0100 Subject: [PATCH 12/22] Restore range --- .../tool/timeline-legacy/timeline-legacy.js | 1 - .../timeline-legacy/timeline.component.jsx | 214 ------------------ .../tool/timeline-legacy/timeline.hooks.js | 113 --------- .../tool/timeline-legacy/timeline.scss | 211 ----------------- .../tool/timeline/timeline.component.jsx | 152 ++++++++++--- .../tool/timeline/timeline.hooks.js | 48 +++- .../timeline.reducer.js | 0 .../tool/timeline/timeline.scss | 70 ++++-- 8 files changed, 227 insertions(+), 582 deletions(-) delete mode 100644 frontend/scripts/react-components/tool/timeline-legacy/timeline-legacy.js delete mode 100644 frontend/scripts/react-components/tool/timeline-legacy/timeline.component.jsx delete mode 100644 frontend/scripts/react-components/tool/timeline-legacy/timeline.hooks.js delete mode 100644 frontend/scripts/react-components/tool/timeline-legacy/timeline.scss rename frontend/scripts/react-components/tool/{timeline-legacy => timeline}/timeline.reducer.js (100%) diff --git a/frontend/scripts/react-components/tool/timeline-legacy/timeline-legacy.js b/frontend/scripts/react-components/tool/timeline-legacy/timeline-legacy.js deleted file mode 100644 index 75923cc5b4..0000000000 --- a/frontend/scripts/react-components/tool/timeline-legacy/timeline-legacy.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './timeline.component'; diff --git a/frontend/scripts/react-components/tool/timeline-legacy/timeline.component.jsx b/frontend/scripts/react-components/tool/timeline-legacy/timeline.component.jsx deleted file mode 100644 index 352fd8e5a9..0000000000 --- a/frontend/scripts/react-components/tool/timeline-legacy/timeline.component.jsx +++ /dev/null @@ -1,214 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import Tabs from 'react-components/shared/tabs'; -import Text from 'react-components/shared/text/text.component'; -import _range from 'lodash/range'; -import Tooltip from 'react-components/shared/help-tooltip/help-tooltip.component'; - -import { translateText } from 'utils/transifex'; - -import { - useTimelineReducer, - useSelectedYearsPropsState, - useUpdateSelectedYears, - useEscapeClearEvent, - useSlider -} from './timeline.hooks'; - -import './timeline.scss'; - -function getClassName(year, state) { - if (state.range) { - const classes = []; - - if (state.start && state.end) { - if (year > state.start && year < state.end) { - classes.push('-active'); - } else if (year === state.start) { - classes.push('-start'); - } else if (year === state.end) { - classes.push('-end'); - } - } else if (state.start) { - if (state.start === year) { - classes.push('-start'); - } else { - const [startYear, endYear] = [state.start, state.hovered].sort(); - if (_range(startYear, (endYear || startYear) + 1).includes(year)) { - classes.push('-active'); - } - } - } else if (year === state.hovered) { - classes.push('-active'); - } - return classes.join(' '); - } - - if (year === state.start && year === state.end) { - return '-start'; - } - if (year === state.hovered) { - return '-active'; - } - - return ''; -} - -function Timeline(props) { - const { years, subNationalYears, showBackground, visibleTabs, disabled } = props; - const [state, dispatch] = useTimelineReducer(props); - useSelectedYearsPropsState(props, state, dispatch); - useUpdateSelectedYears(props, state); - useEscapeClearEvent(state, dispatch); - const { - refs, - hasNextPage, - hasPrevPage, - transform, - onNext, - onPrevious, - sizes, - MARGIN_BETWEEN_ITEMS, - rangeOutOfBounds - } = useSlider(props); - - const tabs = [ - { label: 'year', payload: false, type: 'toggleRange' }, - { label: 'range', payload: true, type: 'toggleRange' } - ].filter(tab => visibleTabs.includes(tab.label)); - const showPlaceholder = state.start && state.end && state.range && !disabled && rangeOutOfBounds; - - return ( -
- t.payload} - itemTabRenderer={t => t.label} - onSelectTab={item => dispatch(item)} - selectedTab={state.range} - /> -
dispatch({ type: 'togglePlaceholder', payload: true })} - onMouseLeave={() => dispatch({ type: 'togglePlaceholder', payload: false })} - > - {showPlaceholder && ( -
-
- - {state.start} - -
-
- - Change selected years - -
-
- - {state.end} - -
-
- )} - - - ); - })} - -
-
- ); -} - -Timeline.defaultProps = { - showBackground: true, - visibleTabs: ['year', 'range'] -}; - -Timeline.propTypes = { - visibleTabs: PropTypes.array, - showBackground: PropTypes.bool, - years: PropTypes.array.isRequired, - subNationalYears: PropTypes.array.isRequired, - disabled: PropTypes.bool, - selectYears: PropTypes.func.isRequired, // eslint-disable-line - selectedYears: PropTypes.array.isRequired // eslint-disable-line -}; - -export default Timeline; diff --git a/frontend/scripts/react-components/tool/timeline-legacy/timeline.hooks.js b/frontend/scripts/react-components/tool/timeline-legacy/timeline.hooks.js deleted file mode 100644 index 24ae05b728..0000000000 --- a/frontend/scripts/react-components/tool/timeline-legacy/timeline.hooks.js +++ /dev/null @@ -1,113 +0,0 @@ -import { useReducer, useEffect, useRef, useState } from 'react'; -import timelineReducer, { initTimelineState } from './timeline.reducer'; - -export function useTimelineReducer({ selectedYears }) { - return useReducer(timelineReducer, initTimelineState(selectedYears), initTimelineState); -} - -export function useSelectedYearsPropsState(props, state, dispatch) { - useEffect(() => { - if (props.selectedYears.length > 0) { - dispatch({ type: 'reset', payload: props.selectedYears }); - } - }, [props.selectedYears, dispatch]); -} - -export function useUpdateSelectedYears(props, state) { - const { selectYears, selectedYears } = props; - useEffect(() => { - if ( - state.start && - state.end && - (state.start !== selectedYears[0] || state.end !== selectedYears[1]) - ) { - selectYears([state.start, state.end]); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.start, state.end]); -} - -export function useEscapeClearEvent(state, dispatch) { - useEffect(() => { - const onClickEscape = e => { - if (e.key === 'Escape') { - dispatch({ type: 'clear' }); - } - }; - if (!state.end && state.range) { - window.addEventListener('keydown', onClickEscape); - } - - return () => { - window.removeEventListener('keydown', onClickEscape); - }; - }, [state.end, dispatch, state.range]); -} - -export function useSlider({ years, selectedYears }) { - const [page, setPage] = useState(0); - const container = useRef(null); - const contentList = useRef(null); - const item = useRef(null); - const [sizes, setSizes] = useState({ item: 0, page: 0, visible: 0 }); - - const DEFAULT_PAGE_SIZE = 3; - const MARGIN_BETWEEN_ITEMS = 12; // corresponding to timeline.scss values - - const maxVisibleItems = sizes.container ? Math.floor(sizes.container / sizes.item) : 0; - const pointer = page > 0 ? page * DEFAULT_PAGE_SIZE + 1 : 0; - const remainingItems = years.length - pointer; - const rangeOutOfBounds = maxVisibleItems < selectedYears[1] - selectedYears[0]; - const jumpToPage = (_page, pageSize = DEFAULT_PAGE_SIZE) => - -_page * sizes.item * pageSize - (_page > 0 ? MARGIN_BETWEEN_ITEMS * 2 : 0); - - let hasNextPage = remainingItems > maxVisibleItems; - let jump = jumpToPage(page); - if (remainingItems % DEFAULT_PAGE_SIZE > 0 && remainingItems < maxVisibleItems && page > 0) { - hasNextPage = false; - jump = jumpToPage(page - 1) + jumpToPage(1, remainingItems % DEFAULT_PAGE_SIZE); - } - const transform = `translate3d(${jump}px, 0, 0)`; - - useEffect(() => { - if (container.current && contentList.current && item.current) { - const containerBounds = container.current.getBoundingClientRect(); - const listBounds = contentList.current.getBoundingClientRect(); - const itemBounds = item.current.getBoundingClientRect(); - const newSizes = { - item: Math.ceil(itemBounds.width) + MARGIN_BETWEEN_ITEMS, - list: Math.floor(listBounds.width) - MARGIN_BETWEEN_ITEMS, - container: Math.floor(containerBounds.width) - }; - setSizes(newSizes); - } - }, [years]); - - useEffect(() => { - const startYearIndex = years.findIndex(i => i === selectedYears[0]); - const position = startYearIndex / maxVisibleItems + ((startYearIndex / maxVisibleItems) % 1); - const numPages = years.length / maxVisibleItems - 1; - const selectedYearPage = position > 1 ? Math.ceil(position) : Math.floor(position); - const pageToStart = numPages > 0 ? selectedYearPage : 0; - setPage(pageToStart); - - // we want to recalculate the active page only when the sizes change. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sizes, years]); - - return { - refs: { - container, - contentList, - item - }, - sizes, - MARGIN_BETWEEN_ITEMS, - transform, - hasNextPage, - hasPrevPage: page > 0, - onPrevious: () => setPage(p => p - 1), - onNext: () => setPage(p => p + 1), - rangeOutOfBounds - }; -} diff --git a/frontend/scripts/react-components/tool/timeline-legacy/timeline.scss b/frontend/scripts/react-components/tool/timeline-legacy/timeline.scss deleted file mode 100644 index 8fa8eb4e76..0000000000 --- a/frontend/scripts/react-components/tool/timeline-legacy/timeline.scss +++ /dev/null @@ -1,211 +0,0 @@ -@import 'styles/settings'; -@import 'styles/mixins'; - -.c-timeline { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 0 40px; - bottom: -70px; - - @media screen and (min-width: $breakpoint-laptop) { - bottom: 0; - } - - left: 0; - width: 100%; - height: $timeline-height; - z-index: $z-above; - - &.-show-background { - background-color: $background-white; - box-shadow: $box-shadow; - } - - .timeline-container { - display: flex; - align-items: center; - justify-content: flex-start; - margin: 0 0 0 12px; - overflow: hidden; - flex: 1; - position: relative; - border-radius: 4px; - - &::before, - &::after { - display: block; - position: absolute; - height: 30px; - width: 60px; - top: 0; - z-index: $z-base; - transform: translate(-50%, 0); - pointer-events: none; - } - - &::before { - left: 30px; - background-image: linear-gradient( - to right, - $background-white 70%, - rgba($background-white, 0.2) 100% - ); - } - - &::after { - left: calc(100% - 30px); - background-image: linear-gradient( - to left, - $background-white 70%, - rgba($background-white, 0.2) 100% - ); - } - - &.-button-left::before { - content: ''; - } - - &.-button-right::after { - content: ''; - } - } - - .timeline-page-button { - position: absolute; - height: 30px; - width: 30px; - z-index: $z-above; - background-color: $charcoal-grey; - border-radius: 4px; - cursor: pointer; - opacity: 0; - pointer-events: none; - transition: opacity 350ms $ease-in-out-sine; - - &::after { - content: ''; - display: block; - position: absolute; - width: 8px; - height: 8px; - top: 50%; - left: 50%; - border: 2px solid $white; - border-bottom: 0; - border-left: 0; - } - - &.-prev { - top: 0; - left: 0; - &::after { - transform: translate(calc(-50% + 1px), -50%) rotate(225deg); - } - } - - &.-next { - top: 0; - right: 0; - &::after { - transform: translate(calc(-50% - 1px), -50%) rotate(45deg); - } - } - - &.-visible { - opacity: 1; - pointer-events: all; - } - } - - .timeline-years-list { - display: flex; - transition: transform 350ms $ease-in-out-sine; - } - - .timeline-year-item { - border: 1px solid $charcoal-grey-faded; - margin: 0 6px; // matches MARGIN_BETWEEN_ITEMS inside useSlider - height: 30px; - width: 75px; - border-radius: 4px; - background-color: $white; - - &.-sub-national { - border: 2px solid $strong-pink; - &.-start, - &.-end { - background-color: $strong-pink; - } - } - - &.-active { - background-color: $charcoal-grey-faded; - &.-sub-national { - background-color: $strong-pink; - color: $white; - } - } - - &.-start, - &.-end { - background-color: $charcoal-grey; - } - - .timeline-year-button { - display: flex; - align-items: center; - justify-content: center; - padding: 6px 12px; - height: 100%; - width: 100%; - cursor: pointer; - - &[disabled=''] { - cursor: default; - } - } - } - - .timeline-range-placeholder { - position: absolute; - height: 30px; - left: 0; - top: 0; - display: flex; - align-items: center; - justify-content: space-between; - z-index: $z-above-hero; - background-color: darken($background-white, 7%); - border-radius: 4px; - opacity: 1; - transition: opacity 350ms $ease-in-out-sine; - margin: 0 6px; - - &.-hidden { - opacity: 0; - pointer-events: none; - } - - .timeline-placeholder-year-item { - display: flex; - align-items: center; - justify-content: center; - height: 30px; - width: 58px; - border-radius: 4px; - background-color: $charcoal-grey; - padding: 6px 12px; - flex-shrink: 0; - - > span { - display: block; - } - } - - .timeline-placeholder-text { - pointer-events: none; - } - } -} diff --git a/frontend/scripts/react-components/tool/timeline/timeline.component.jsx b/frontend/scripts/react-components/tool/timeline/timeline.component.jsx index 05f9b6b6f9..352fd8e5a9 100644 --- a/frontend/scripts/react-components/tool/timeline/timeline.component.jsx +++ b/frontend/scripts/react-components/tool/timeline/timeline.component.jsx @@ -1,51 +1,143 @@ -import React, { useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import Text from 'react-components/shared/text'; -import Icon from 'react-components/shared/icon'; +import Tabs from 'react-components/shared/tabs'; +import Text from 'react-components/shared/text/text.component'; +import _range from 'lodash/range'; import Tooltip from 'react-components/shared/help-tooltip/help-tooltip.component'; import { translateText } from 'utils/transifex'; -import { useSlider } from './timeline.hooks'; +import { + useTimelineReducer, + useSelectedYearsPropsState, + useUpdateSelectedYears, + useEscapeClearEvent, + useSlider +} from './timeline.hooks'; import './timeline.scss'; +function getClassName(year, state) { + if (state.range) { + const classes = []; + + if (state.start && state.end) { + if (year > state.start && year < state.end) { + classes.push('-active'); + } else if (year === state.start) { + classes.push('-start'); + } else if (year === state.end) { + classes.push('-end'); + } + } else if (state.start) { + if (state.start === year) { + classes.push('-start'); + } else { + const [startYear, endYear] = [state.start, state.hovered].sort(); + if (_range(startYear, (endYear || startYear) + 1).includes(year)) { + classes.push('-active'); + } + } + } else if (year === state.hovered) { + classes.push('-active'); + } + return classes.join(' '); + } + + if (year === state.start && year === state.end) { + return '-start'; + } + if (year === state.hovered) { + return '-active'; + } + + return ''; +} + function Timeline(props) { - const { years, subNationalYears, showBackground, disabled, selectYears, selectedYears } = props; + const { years, subNationalYears, showBackground, visibleTabs, disabled } = props; + const [state, dispatch] = useTimelineReducer(props); + useSelectedYearsPropsState(props, state, dispatch); + useUpdateSelectedYears(props, state); + useEscapeClearEvent(state, dispatch); + const { + refs, + hasNextPage, + hasPrevPage, + transform, + onNext, + onPrevious, + sizes, + MARGIN_BETWEEN_ITEMS, + rangeOutOfBounds + } = useSlider(props); + + const tabs = [ + { label: 'year', payload: false, type: 'toggleRange' }, + { label: 'range', payload: true, type: 'toggleRange' } + ].filter(tab => visibleTabs.includes(tab.label)); + const showPlaceholder = state.start && state.end && state.range && !disabled && rangeOutOfBounds; - const { refs, hasNextPage, hasPrevPage, transform, onNext, onPrevious } = useSlider(props); - const [hoveredYear, setHoveredYear] = useState(); return (
- - - YEAR - + t.payload} + itemTabRenderer={t => t.label} + onSelectTab={item => dispatch(item)} + selectedTab={state.range} + />
dispatch({ type: 'togglePlaceholder', payload: true })} + onMouseLeave={() => dispatch({ type: 'togglePlaceholder', payload: false })} > + {showPlaceholder && ( +
+
+ + {state.start} + +
+
+ + Change selected years + +
+
+ + {state.end} + +
+
+ )}
diff --git a/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss index fe25df328e..84a5336b63 100644 --- a/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss +++ b/frontend/scripts/react-components/shared/methods-disclaimer-banner/methods-disclaimer-banner.scss @@ -27,16 +27,11 @@ .close-button { position: absolute; - right: 30px; - top: 40%; + right: 242px; + top: 31%; border: none; } - .icon-close { - width: 20px; - height: 20px; - } - .link { text-decoration: underline; font-weight: $font-weight-bold; From 9efe4d0656698637b5fac2aa1fdf19179fb1f468 Mon Sep 17 00:00:00 2001 From: Alvaro Leal Date: Wed, 7 Dec 2022 02:28:20 +0100 Subject: [PATCH 22/22] Fix unreachable button on popup --- frontend/scripts/react-components/popup/popup-styles.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/scripts/react-components/popup/popup-styles.scss b/frontend/scripts/react-components/popup/popup-styles.scss index a78562120e..065fc011b4 100644 --- a/frontend/scripts/react-components/popup/popup-styles.scss +++ b/frontend/scripts/react-components/popup/popup-styles.scss @@ -1,6 +1,9 @@ @import 'styles/settings'; .c-popup { + max-height: 90vh; + overflow: scroll; + .popup-content { padding: 40px; z-index: $z-above-hero;