From 0d372d74270f1b131c350f690811be96a3d40019 Mon Sep 17 00:00:00 2001
From: Daniel Bachler <daniel@danielbachler.de>
Date: Tue, 24 Oct 2023 14:48:37 +0200
Subject: [PATCH] data-page-shared-sources

---
 baker/formatWordpressPost.tsx                 |   2 +-
 .../src/CodeSnippet}/CodeSnippet.stories.tsx  |   0
 .../src/CodeSnippet}/CodeSnippet.tsx          |   0
 .../src/CodeSnippet}/code-snippet.scss        |   0
 .../src/IndicatorBrief/IndicatorBrief.tsx     |   6 +-
 .../src/IndicatorSources/IndicatorSources.tsx | 122 +++++++++++++++
 .../src/SharedDataPageConstants.ts}           |   1 +
 .../@ourworldindata/components/src/index.ts   |  16 ++
 .../grapher/src/footer/Footer.tsx             |   9 +-
 packages/@ourworldindata/utils/src/index.ts   |   2 -
 site/DataPageContent.tsx                      |  12 +-
 site/DataPageV2Content.tsx                    | 144 +-----------------
 site/LongFormPage.tsx                         |   2 +-
 site/gdocs/OwidGdoc.tsx                       |   2 +-
 site/owid.scss                                |   2 +-
 site/runSiteFooterScripts.ts                  |   2 +-
 16 files changed, 163 insertions(+), 159 deletions(-)
 rename {site/blocks => packages/@ourworldindata/components/src/CodeSnippet}/CodeSnippet.stories.tsx (100%)
 rename {site/blocks => packages/@ourworldindata/components/src/CodeSnippet}/CodeSnippet.tsx (100%)
 rename {site/blocks => packages/@ourworldindata/components/src/CodeSnippet}/code-snippet.scss (100%)
 create mode 100644 packages/@ourworldindata/components/src/IndicatorSources/IndicatorSources.tsx
 rename packages/@ourworldindata/{utils/src/SharedConstants.ts => components/src/SharedDataPageConstants.ts} (61%)

diff --git a/baker/formatWordpressPost.tsx b/baker/formatWordpressPost.tsx
index 67db2683f2c..0be77af7b6f 100644
--- a/baker/formatWordpressPost.tsx
+++ b/baker/formatWordpressPost.tsx
@@ -49,7 +49,7 @@ import {
     renderAdditionalInformation,
 } from "../site/blocks/AdditionalInformation.js"
 import { renderHelp } from "../site/blocks/Help.js"
-import { renderCodeSnippets } from "../site/blocks/CodeSnippet.js"
+import { renderCodeSnippets } from "@ourworldindata/components"
 import { renderExpandableParagraphs } from "../site/blocks/ExpandableParagraph.js"
 import {
     formatUrls,
diff --git a/site/blocks/CodeSnippet.stories.tsx b/packages/@ourworldindata/components/src/CodeSnippet/CodeSnippet.stories.tsx
similarity index 100%
rename from site/blocks/CodeSnippet.stories.tsx
rename to packages/@ourworldindata/components/src/CodeSnippet/CodeSnippet.stories.tsx
diff --git a/site/blocks/CodeSnippet.tsx b/packages/@ourworldindata/components/src/CodeSnippet/CodeSnippet.tsx
similarity index 100%
rename from site/blocks/CodeSnippet.tsx
rename to packages/@ourworldindata/components/src/CodeSnippet/CodeSnippet.tsx
diff --git a/site/blocks/code-snippet.scss b/packages/@ourworldindata/components/src/CodeSnippet/code-snippet.scss
similarity index 100%
rename from site/blocks/code-snippet.scss
rename to packages/@ourworldindata/components/src/CodeSnippet/code-snippet.scss
diff --git a/packages/@ourworldindata/components/src/IndicatorBrief/IndicatorBrief.tsx b/packages/@ourworldindata/components/src/IndicatorBrief/IndicatorBrief.tsx
index cec0ada9257..df53055a12b 100644
--- a/packages/@ourworldindata/components/src/IndicatorBrief/IndicatorBrief.tsx
+++ b/packages/@ourworldindata/components/src/IndicatorBrief/IndicatorBrief.tsx
@@ -3,10 +3,8 @@ import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown"
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
 import { ExpandableToggle } from "../ExpandableToggle/ExpandableToggle.js"
 import { SimpleMarkdownText } from "../SimpleMarkdownText.js"
-import {
-    DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
-    dayjs,
-} from "@ourworldindata/utils"
+import { dayjs } from "@ourworldindata/utils"
+import { DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID } from "../SharedDataPageConstants.js"
 
 interface IndicatorBriefProps {
     descriptionShort: string | undefined
diff --git a/packages/@ourworldindata/components/src/IndicatorSources/IndicatorSources.tsx b/packages/@ourworldindata/components/src/IndicatorSources/IndicatorSources.tsx
new file mode 100644
index 00000000000..77a592ac5e3
--- /dev/null
+++ b/packages/@ourworldindata/components/src/IndicatorSources/IndicatorSources.tsx
@@ -0,0 +1,122 @@
+import React from "react"
+import { ExpandableToggle } from "../ExpandableToggle/ExpandableToggle.js"
+import { OwidOrigin } from "@ourworldindata/utils"
+import { SimpleMarkdownText } from "../SimpleMarkdownText.js"
+import { CodeSnippet } from "../CodeSnippet/CodeSnippet.js"
+import { REUSE_THIS_WORK_SECTION_ID } from "../SharedDataPageConstants.js"
+
+export type OriginSubset = Pick<
+    OwidOrigin,
+    | "producer"
+    | "descriptionSnapshot"
+    | "dateAccessed"
+    | "urlMain"
+    | "description"
+    | "citationFull"
+>
+
+export interface IndicatorSourcesProps {
+    origins: OriginSubset[]
+}
+
+export const IndicatorSources = (props: IndicatorSourcesProps) => {
+    const citationFullBlockFn = (source: OriginSubset) => {
+        source.citationFull && (
+            <div
+                className="key-data"
+                style={{
+                    gridColumn: "span 2",
+                }}
+            >
+                <div className="key-data__title--dark">Citation</div>
+                This is the citation of the original data obtained from the
+                source, prior to any processing or adaptation by Our World in
+                Data. To cite data downloaded from this page, please use the
+                suggested citation given in{" "}
+                <a href={"#" + REUSE_THIS_WORK_SECTION_ID}>
+                    Reuse This Work
+                </a>{" "}
+                below.
+                <CodeSnippet code={source.citationFull} theme="light" />
+            </div>
+        )
+    }
+    return (
+        <div className="data-sources grid span-cols-12">
+            <h3 className="data-sources__heading span-cols-2 span-lg-cols-3 col-md-start-2 span-md-cols-10 col-sm-start-1 span-sm-cols-12">
+                This data is based on the following sources
+            </h3>
+            <div className="col-start-4 span-cols-6 col-lg-start-5 span-lg-cols-7 col-md-start-2 span-md-cols-10 col-sm-start-1 span-sm-cols-12">
+                {props.origins.map((source, idx: number, sources) => {
+                    return (
+                        <div className="data-sources__source-item" key={idx}>
+                            <ExpandableToggle
+                                label={
+                                    source.producer ??
+                                    source.descriptionSnapshot ??
+                                    source.description ??
+                                    ""
+                                }
+                                isStacked={idx !== sources.length - 1}
+                                hasTeaser
+                                content={
+                                    <>
+                                        {source.description && (
+                                            <p className="article-block__text">
+                                                <SimpleMarkdownText
+                                                    text={source.description}
+                                                />
+                                            </p>
+                                        )}
+                                        {(source.dateAccessed ||
+                                            source.urlMain) && (
+                                            <div
+                                                className="grid source__key-data"
+                                                style={{
+                                                    gridTemplateColumns:
+                                                        "minmax(0,1fr) minmax(0,2fr)",
+                                                }}
+                                            >
+                                                {source.dateAccessed && (
+                                                    <div className="key-data">
+                                                        <div className="key-data__title--dark">
+                                                            Retrieved on
+                                                        </div>
+                                                        <div>
+                                                            {
+                                                                source.dateAccessed
+                                                            }
+                                                        </div>
+                                                    </div>
+                                                )}
+                                                {source.urlMain && (
+                                                    <div className="key-data key-data--hide-overflow">
+                                                        <div className="key-data__title--dark">
+                                                            Retrieved from
+                                                        </div>
+                                                        <div>
+                                                            <a
+                                                                href={
+                                                                    source.urlMain
+                                                                }
+                                                                target="_blank"
+                                                                rel="noreferrer"
+                                                            >
+                                                                {source.urlMain}
+                                                            </a>
+                                                        </div>
+                                                    </div>
+                                                )}
+                                                {citationFullBlockFn(source)}
+                                            </div>
+                                        )}
+                                    </>
+                                }
+                            />
+                        </div>
+                    )
+                })}
+            </div>
+        </div>
+    )
+}
diff --git a/packages/@ourworldindata/utils/src/SharedConstants.ts b/packages/@ourworldindata/components/src/SharedDataPageConstants.ts
similarity index 61%
rename from packages/@ourworldindata/utils/src/SharedConstants.ts
rename to packages/@ourworldindata/components/src/SharedDataPageConstants.ts
index d95b78778fb..9c9be698ec2 100644
--- a/packages/@ourworldindata/utils/src/SharedConstants.ts
+++ b/packages/@ourworldindata/components/src/SharedDataPageConstants.ts
@@ -1,2 +1,3 @@
 export const DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID =
     "sources-and-processing" as const
+export const REUSE_THIS_WORK_SECTION_ID = "reuse-this-work"
diff --git a/packages/@ourworldindata/components/src/index.ts b/packages/@ourworldindata/components/src/index.ts
index f9e4e8757f0..249d02cf71a 100644
--- a/packages/@ourworldindata/components/src/index.ts
+++ b/packages/@ourworldindata/components/src/index.ts
@@ -27,3 +27,19 @@ export {
 export { ExpandableToggle } from "./ExpandableToggle/ExpandableToggle.js"
 
 export { IndicatorBrief } from "./IndicatorBrief/IndicatorBrief.js"
+
+export {
+    IndicatorSources,
+    type OriginSubset,
+} from "./IndicatorSources/IndicatorSources.js"
+
+export {
+    CodeSnippet,
+    hydrateCodeSnippets,
+    renderCodeSnippets,
+} from "./CodeSnippet/CodeSnippet.js"
+
+export {
+    DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
+    REUSE_THIS_WORK_SECTION_ID,
+} from "./SharedDataPageConstants.js"
diff --git a/packages/@ourworldindata/grapher/src/footer/Footer.tsx b/packages/@ourworldindata/grapher/src/footer/Footer.tsx
index dcb99db62ba..d7d469596bf 100644
--- a/packages/@ourworldindata/grapher/src/footer/Footer.tsx
+++ b/packages/@ourworldindata/grapher/src/footer/Footer.tsx
@@ -2,13 +2,12 @@ import React from "react"
 import { observable, computed, action } from "mobx"
 import { observer } from "mobx-react"
 import parseUrl from "url-parse"
+import { Bounds, DEFAULT_BOUNDS, getRelativeMouse } from "@ourworldindata/utils"
 import {
-    Bounds,
-    DEFAULT_BOUNDS,
-    getRelativeMouse,
     DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
-} from "@ourworldindata/utils"
-import { MarkdownTextWrap, TextWrap } from "@ourworldindata/components"
+    MarkdownTextWrap,
+    TextWrap,
+} from "@ourworldindata/components"
 import { Tooltip } from "../tooltip/Tooltip"
 import { FooterManager } from "./FooterManager"
 import { ActionButtons } from "../controls/ActionButtons"
diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts
index 5cdf2bd106e..51f21bb14d7 100644
--- a/packages/@ourworldindata/utils/src/index.ts
+++ b/packages/@ourworldindata/utils/src/index.ts
@@ -611,5 +611,3 @@ export {
     gdocIdRegex,
     detailOnDemandRegex,
 } from "./GdocsConstants.js"
-
-export { DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID } from "./SharedConstants"
diff --git a/site/DataPageContent.tsx b/site/DataPageContent.tsx
index 2d36372cc3c..c08ef7bfaae 100644
--- a/site/DataPageContent.tsx
+++ b/site/DataPageContent.tsx
@@ -2,21 +2,21 @@ import React, { useEffect } from "react"
 import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown"
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
 import { Grapher, GrapherInterface } from "@ourworldindata/grapher"
-import { ExpandableToggle } from "@ourworldindata/components"
+import {
+    ExpandableToggle,
+    CodeSnippet,
+    DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
+} from "@ourworldindata/components"
 import ReactDOM from "react-dom"
 import { GrapherWithFallback } from "./GrapherWithFallback.js"
 import { formatAuthors } from "./clientFormatting.js"
 import { ArticleBlocks } from "./gdocs/ArticleBlocks.js"
 import { RelatedCharts } from "./blocks/RelatedCharts.js"
-import {
-    DataPageContentFields,
-    DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
-} from "@ourworldindata/utils"
+import { DataPageContentFields } from "@ourworldindata/utils"
 import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js"
 import StickyNav from "./blocks/StickyNav.js"
 import cx from "classnames"
 import { DebugProvider } from "./gdocs/DebugContext.js"
-import { CodeSnippet } from "./blocks/CodeSnippet.js"
 import { DATA_API_URL } from "../settings/clientSettings.js"
 
 declare global {
diff --git a/site/DataPageV2Content.tsx b/site/DataPageV2Content.tsx
index a4e5e3c821d..0b6cebc6839 100644
--- a/site/DataPageV2Content.tsx
+++ b/site/DataPageV2Content.tsx
@@ -1,9 +1,13 @@
 import React, { useEffect } from "react"
 import { Grapher, GrapherInterface } from "@ourworldindata/grapher"
 import {
-    ExpandableToggle,
     markdownToEnrichedTextBlock,
     IndicatorBrief,
+    CodeSnippet,
+    REUSE_THIS_WORK_SECTION_ID,
+    OriginSubset,
+    IndicatorSources,
+    DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
 } from "@ourworldindata/components"
 import ReactDOM from "react-dom"
 import { GrapherWithFallback } from "./GrapherWithFallback.js"
@@ -12,17 +16,14 @@ import { RelatedCharts } from "./blocks/RelatedCharts.js"
 import {
     DataPageV2ContentFields,
     slugify,
-    DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
     uniq,
     pick,
-    OwidOrigin,
     formatAuthors,
 } from "@ourworldindata/utils"
 import { AttachmentsContext, DocumentContext } from "./gdocs/OwidGdoc.js"
 import StickyNav from "./blocks/StickyNav.js"
 import cx from "classnames"
 import { DebugProvider } from "./gdocs/DebugContext.js"
-import { CodeSnippet } from "./blocks/CodeSnippet.js"
 import dayjs from "dayjs"
 declare global {
     interface Window {
@@ -85,16 +86,6 @@ export const slugify_topic = (topic: string) => {
     return slugify(replaced)
 }
 
-type OriginSubset = Pick<
-    OwidOrigin,
-    | "producer"
-    | "descriptionSnapshot"
-    | "dateAccessed"
-    | "urlMain"
-    | "description"
-    | "citationFull"
->
-
 export const DataPageV2Content = ({
     datapageData,
     grapherConfig,
@@ -118,8 +109,6 @@ export const DataPageV2Content = ({
         setGrapher(new Grapher(mergedGrapherConfig))
     }, [mergedGrapherConfig])
 
-    const REUSE_THIS_WORK_ANCHOR = "#reuse-this-work"
-
     const stickyNavLinks = [
         {
             text: "Explore the Data",
@@ -136,7 +125,7 @@ export const DataPageV2Content = ({
             text: "Sources & Processing",
             target: "#" + DATAPAGE_SOURCES_AND_PROCESSING_SECTION_ID,
         },
-        { text: "Reuse This Work", target: REUSE_THIS_WORK_ANCHOR },
+        { text: "Reuse This Work", target: "#" + REUSE_THIS_WORK_SECTION_ID },
     ]
 
     const hasRelatedDataFeatured = datapageData.relatedData?.some(
@@ -195,25 +184,6 @@ export const DataPageV2Content = ({
         relatedCharts = [],
     } = faqEntries ?? {}
 
-    const citationFullBlockFn = (source: OriginSubset) => {
-        source.citationFull && (
-            <div
-                className="key-data"
-                style={{
-                    gridColumn: "span 2",
-                }}
-            >
-                <div className="key-data__title--dark">Citation</div>
-                This is the citation of the original data obtained from the
-                source, prior to any processing or adaptation by Our World in
-                Data. To cite data downloaded from this page, please use the
-                suggested citation given in{" "}
-                <a href={REUSE_THIS_WORK_ANCHOR}>Reuse This Work</a> below.
-                <CodeSnippet code={source.citationFull} theme="light" />
-            </div>
-        )
-    }
-
     const dateRange = getDateRange(datapageData.dateRange)
 
     const citationDatapage = datapageData.primaryTopic
@@ -472,107 +442,7 @@ export const DataPageV2Content = ({
                                 >
                                     Sources and processing
                                 </h2>
-                                {origins.length > 0 && (
-                                    <div className="data-sources grid span-cols-12">
-                                        <h3 className="data-sources__heading span-cols-2 span-lg-cols-3 col-md-start-2 span-md-cols-10 col-sm-start-1 span-sm-cols-12">
-                                            This data is based on the following
-                                            sources
-                                        </h3>
-                                        <div className="col-start-4 span-cols-6 col-lg-start-5 span-lg-cols-7 col-md-start-2 span-md-cols-10 col-sm-start-1 span-sm-cols-12">
-                                            {origins.map(
-                                                (
-                                                    source,
-                                                    idx: number,
-                                                    sources
-                                                ) => {
-                                                    return (
-                                                        <div
-                                                            className="data-sources__source-item"
-                                                            key={idx}
-                                                        >
-                                                            <ExpandableToggle
-                                                                label={
-                                                                    source.producer ??
-                                                                    source.descriptionSnapshot ??
-                                                                    source.description ??
-                                                                    ""
-                                                                }
-                                                                isStacked={
-                                                                    idx !==
-                                                                    sources.length -
-                                                                        1
-                                                                }
-                                                                hasTeaser
-                                                                content={
-                                                                    <>
-                                                                        {source.description && (
-                                                                            <ArticleBlocks
-                                                                                blocks={[
-                                                                                    markdownToEnrichedTextBlock(
-                                                                                        source.description
-                                                                                    ),
-                                                                                ]}
-                                                                                containerType="datapage"
-                                                                            />
-                                                                        )}
-                                                                        {(source.dateAccessed ||
-                                                                            source.urlMain) && (
-                                                                            <div
-                                                                                className="grid source__key-data"
-                                                                                style={{
-                                                                                    gridTemplateColumns:
-                                                                                        "minmax(0,1fr) minmax(0,2fr)",
-                                                                                }}
-                                                                            >
-                                                                                {source.dateAccessed && (
-                                                                                    <div className="key-data">
-                                                                                        <div className="key-data__title--dark">
-                                                                                            Retrieved
-                                                                                            on
-                                                                                        </div>
-                                                                                        <div>
-                                                                                            {
-                                                                                                source.dateAccessed
-                                                                                            }
-                                                                                        </div>
-                                                                                    </div>
-                                                                                )}
-                                                                                {source.urlMain && (
-                                                                                    <div className="key-data key-data--hide-overflow">
-                                                                                        <div className="key-data__title--dark">
-                                                                                            Retrieved
-                                                                                            from
-                                                                                        </div>
-                                                                                        <div>
-                                                                                            <a
-                                                                                                href={
-                                                                                                    source.urlMain
-                                                                                                }
-                                                                                                target="_blank"
-                                                                                                rel="noreferrer"
-                                                                                            >
-                                                                                                {
-                                                                                                    source.urlMain
-                                                                                                }
-                                                                                            </a>
-                                                                                        </div>
-                                                                                    </div>
-                                                                                )}
-                                                                                {citationFullBlockFn(
-                                                                                    source
-                                                                                )}
-                                                                            </div>
-                                                                        )}
-                                                                    </>
-                                                                }
-                                                            />
-                                                        </div>
-                                                    )
-                                                }
-                                            )}
-                                        </div>
-                                    </div>
-                                )}
+                                <IndicatorSources origins={origins} />
                                 <div className="data-processing grid span-cols-12">
                                     <h3 className="data-processing__heading span-cols-2 span-lg-cols-3 col-md-start-2 span-md-cols-10 col-sm-start-1 span-sm-cols-12">
                                         How we process data at Our World in Data
diff --git a/site/LongFormPage.tsx b/site/LongFormPage.tsx
index be87646c754..79b65812312 100644
--- a/site/LongFormPage.tsx
+++ b/site/LongFormPage.tsx
@@ -21,7 +21,7 @@ import { Byline } from "./Byline.js"
 import { PageInfo } from "./PageInfo.js"
 import { BackToTopic } from "./BackToTopic.js"
 import StickyNav from "./blocks/StickyNav.js"
-import { CodeSnippet } from "./blocks/CodeSnippet.js"
+import { CodeSnippet } from "@ourworldindata/components"
 import { formatAuthors } from "./clientFormatting.js"
 
 export interface PageOverrides {
diff --git a/site/gdocs/OwidGdoc.tsx b/site/gdocs/OwidGdoc.tsx
index 990e734e17a..b5a36005043 100644
--- a/site/gdocs/OwidGdoc.tsx
+++ b/site/gdocs/OwidGdoc.tsx
@@ -15,7 +15,7 @@ import {
     OwidGdocType,
     formatAuthors,
 } from "@ourworldindata/utils"
-import { CodeSnippet } from "../blocks/CodeSnippet.js"
+import { CodeSnippet } from "@ourworldindata/components"
 import { BAKED_BASE_URL } from "../../settings/clientSettings.js"
 import { DebugProvider } from "./DebugContext.js"
 import { OwidGdocHeader } from "./OwidGdocHeader.js"
diff --git a/site/owid.scss b/site/owid.scss
index 0ff04b236cf..05dc347a499 100644
--- a/site/owid.scss
+++ b/site/owid.scss
@@ -70,7 +70,7 @@
 @import "./blocks/CookiePreferences.scss";
 @import "./blocks/Grid.scss";
 @import "./blocks/Card.scss";
-@import "./blocks/code-snippet.scss";
+@import "../packages/@ourworldindata/components/src/CodeSnippet/code-snippet.scss";
 @import "./blocks/BiographyCard.scss";
 @import "./blocks/KeyInsights.scss";
 @import "./blocks/TechnicalText.scss";
diff --git a/site/runSiteFooterScripts.ts b/site/runSiteFooterScripts.ts
index 596419821b5..0c9d6bdf8a0 100644
--- a/site/runSiteFooterScripts.ts
+++ b/site/runSiteFooterScripts.ts
@@ -16,7 +16,7 @@ import { runSearchCountry } from "./SearchCountry.js"
 import { hydrate as hydrateAdditionalInformation } from "./blocks/AdditionalInformation.js"
 import { hydrateKeyInsights } from "./blocks/KeyInsights.js"
 import { hydrateExpandableParagraphs } from "./blocks/ExpandableParagraph.js"
-import { hydrateCodeSnippets } from "./blocks/CodeSnippet.js"
+import { hydrateCodeSnippets } from "@ourworldindata/components"
 import { hydrateStickyNav } from "./blocks/StickyNav.js"
 
 export const runSiteFooterScripts = (