From c3db09d8e455642f1bcb4f32b3b2da4babee6dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ra=C4=8D=C3=A1k?= Date: Tue, 23 Jul 2024 15:40:03 +0200 Subject: [PATCH] Add data insights newsletter --- .../components/src/Button/Button.tsx | 2 +- .../components/src/TextInput.scss | 17 +++ .../components/src/TextInput.tsx | 19 +++ .../@ourworldindata/components/src/index.ts | 1 + .../components/src/styles/typography.scss | 12 ++ .../core-table/src/CoreTableColumns.test.ts | 2 +- .../controls/entityPicker/EntityPicker.tsx | 2 +- site/DataInsightsIndexPageContent.tsx | 4 +- site/DataInsightsNewsletterBanner.scss | 18 +++ site/DataInsightsNewsletterBanner.tsx | 24 +++ site/NewsletterSubscription.tsx | 31 ++-- site/SiteNavigation.tsx | 2 +- site/css/newsletter-subscription.scss | 8 - .../components/DataInsightsNewsletter.scss | 141 ++++++++++++++++++ .../components/DataInsightsNewsletter.tsx | 125 ++++++++++++++++ site/gdocs/components/LatestDataInsights.scss | 4 + .../components/LatestDataInsightsBlock.tsx | 2 + site/gdocs/pages/DataInsight.tsx | 2 + site/gdocs/pages/Homepage.scss | 4 - site/owid.scss | 3 + 20 files changed, 391 insertions(+), 32 deletions(-) create mode 100644 packages/@ourworldindata/components/src/TextInput.scss create mode 100644 packages/@ourworldindata/components/src/TextInput.tsx create mode 100644 site/DataInsightsNewsletterBanner.scss create mode 100644 site/DataInsightsNewsletterBanner.tsx create mode 100644 site/gdocs/components/DataInsightsNewsletter.scss create mode 100644 site/gdocs/components/DataInsightsNewsletter.tsx diff --git a/packages/@ourworldindata/components/src/Button/Button.tsx b/packages/@ourworldindata/components/src/Button/Button.tsx index af598ae5ff8..a0499f90fbc 100644 --- a/packages/@ourworldindata/components/src/Button/Button.tsx +++ b/packages/@ourworldindata/components/src/Button/Button.tsx @@ -17,7 +17,7 @@ type WithHrefProps = { } type WithOnClickProps = { - onClick: () => void + onClick?: () => void href?: never } diff --git a/packages/@ourworldindata/components/src/TextInput.scss b/packages/@ourworldindata/components/src/TextInput.scss new file mode 100644 index 00000000000..5ca04142619 --- /dev/null +++ b/packages/@ourworldindata/components/src/TextInput.scss @@ -0,0 +1,17 @@ +.text-input { + @include body-3-medium; + padding: 8px 16px; + color: $blue-90; + border: 1px solid $blue-20; + border-radius: 0; + height: 40px; + + &:focus { + outline: none; + border: 1px solid $blue-40; + } + + &::placeholder { + color: $blue-40; + } +} diff --git a/packages/@ourworldindata/components/src/TextInput.tsx b/packages/@ourworldindata/components/src/TextInput.tsx new file mode 100644 index 00000000000..4376523a2ed --- /dev/null +++ b/packages/@ourworldindata/components/src/TextInput.tsx @@ -0,0 +1,19 @@ +import * as React from "react" +import { forwardRef } from "react" +import cn from "classnames" + +export type InputProps = React.InputHTMLAttributes & { + className?: string +} + +export const TextInput = forwardRef( + function Input({ className, ...props }: InputProps, ref) { + return ( + + ) + } +) diff --git a/packages/@ourworldindata/components/src/index.ts b/packages/@ourworldindata/components/src/index.ts index e651e800f6a..bf0a4b12e14 100644 --- a/packages/@ourworldindata/components/src/index.ts +++ b/packages/@ourworldindata/components/src/index.ts @@ -34,6 +34,7 @@ export { IndicatorProcessing } from "./IndicatorProcessing/IndicatorProcessing.j export { Checkbox } from "./Checkbox.js" export { IndicatorSources } from "./IndicatorSources/IndicatorSources.js" +export { TextInput } from "./TextInput.js" export { CodeSnippet, diff --git a/packages/@ourworldindata/components/src/styles/typography.scss b/packages/@ourworldindata/components/src/styles/typography.scss index e97e70671be..06307183921 100644 --- a/packages/@ourworldindata/components/src/styles/typography.scss +++ b/packages/@ourworldindata/components/src/styles/typography.scss @@ -418,3 +418,15 @@ body { .label-2-medium { @include label-2-medium; } + +@mixin note-1-medium { + font-family: $sans-serif-font-stack; + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.01em; + line-height: 1.33; +} + +.note-1-medium { + @include note-1-medium; +} diff --git a/packages/@ourworldindata/core-table/src/CoreTableColumns.test.ts b/packages/@ourworldindata/core-table/src/CoreTableColumns.test.ts index 90acfd2a9c3..d872ffee8f6 100644 --- a/packages/@ourworldindata/core-table/src/CoreTableColumns.test.ts +++ b/packages/@ourworldindata/core-table/src/CoreTableColumns.test.ts @@ -19,7 +19,7 @@ describe(ColumnTypeNames.Quarter, () => { it("should parse and format values correctly", () => { const testValues = [ - // Input string, parsed value, formatted output string + // TextInput string, parsed value, formatted output string ["2020-Q3", 8082, "Q3/2020"], ["2000-Q1", 8000, "Q1/2000"], ["02000-Q1", 8000, "Q1/2000"], diff --git a/packages/@ourworldindata/grapher/src/controls/entityPicker/EntityPicker.tsx b/packages/@ourworldindata/grapher/src/controls/entityPicker/EntityPicker.tsx index ec90e699d10..a459478882e 100644 --- a/packages/@ourworldindata/grapher/src/controls/entityPicker/EntityPicker.tsx +++ b/packages/@ourworldindata/grapher/src/controls/entityPicker/EntityPicker.tsx @@ -326,7 +326,7 @@ export class EntityPicker extends React.Component<{ switch (event.key) { case "Enter": if (event.keyCode === 229) { - // ignore the keydown event from an Input Method Editor(IME) + // ignore the keydown event from an TextInput Method Editor(IME) // ref. https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode break } diff --git a/site/DataInsightsIndexPageContent.tsx b/site/DataInsightsIndexPageContent.tsx index cc948c6f5aa..813f6a0a64c 100644 --- a/site/DataInsightsIndexPageContent.tsx +++ b/site/DataInsightsIndexPageContent.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { useState, useEffect } from "react" import cx from "classnames" import ReactDOM from "react-dom" import { @@ -16,6 +16,7 @@ import { DataInsightsIndexPageProps } from "./DataInsightsIndexPage.js" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js" import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons" import { DebugProvider } from "./gdocs/DebugContext.js" +import DataInsightsNewsletterBanner from "./DataInsightsNewsletterBanner.js" const Pagination = (props: { pageNumber: number; totalPageCount: number }) => { const { pageNumber, totalPageCount } = props @@ -141,6 +142,7 @@ export const DataInsightsIndexPageContent = ( totalPageCount={props.totalPageCount} pageNumber={props.pageNumber} /> + ) diff --git a/site/DataInsightsNewsletterBanner.scss b/site/DataInsightsNewsletterBanner.scss new file mode 100644 index 00000000000..adb012eca18 --- /dev/null +++ b/site/DataInsightsNewsletterBanner.scss @@ -0,0 +1,18 @@ +.data-insights-newsletter-banner { + position: fixed; + bottom: 0; + left: 0; + right: 0; + transform: translateY(100%); + transition: transform 0.3s ease-out; + opacity: 0; + pointer-events: none; + background-color: $accent-pale-blue; + z-index: 1000; + + &--visible { + transform: translateY(0); + opacity: 1; + pointer-events: auto; + } +} diff --git a/site/DataInsightsNewsletterBanner.tsx b/site/DataInsightsNewsletterBanner.tsx new file mode 100644 index 00000000000..011afedc5f4 --- /dev/null +++ b/site/DataInsightsNewsletterBanner.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import { useState, useEffect } from "react" +import cx from "classnames" +import DataInsightsNewsletter from "./gdocs/components/DataInsightsNewsletter.js" + +export default function DataInsightsNewsletterBanner() { + const [isVisible, setIsVisible] = useState(false) + + useEffect(() => { + const timeoutId = setTimeout(() => { + setIsVisible(true) + }, 3000) + return () => clearTimeout(timeoutId) + }, []) + + return ( + setIsVisible(false)} + /> + ) +} diff --git a/site/NewsletterSubscription.tsx b/site/NewsletterSubscription.tsx index 3813bd1969e..2408259ac52 100644 --- a/site/NewsletterSubscription.tsx +++ b/site/NewsletterSubscription.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react" import { faTimes, faEnvelopeOpenText } from "@fortawesome/free-solid-svg-icons" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js" import { SiteAnalytics } from "./SiteAnalytics.js" +import { TextInput } from "@ourworldindata/components" const analytics = new SiteAnalytics() @@ -64,14 +65,14 @@ export const NewsletterSubscriptionForm = ({ }: { context?: NewsletterSubscriptionContext }) => { - const IMMEDIATE = "1" + const DATA_INSIGHTS = "16" const BIWEEKLY = "2" - const idImmediate = `mce-group[85302]-85302-0${ + const idDataInsights = `mce-group[85302]-85302-0${ context ? "-" + context : "" }` const idBiweekly = `mce-group[85302]-85302-1${context ? "-" + context : ""}` - const [frequencies, setFrequencies] = useState([IMMEDIATE, BIWEEKLY]) + const [frequencies, setFrequencies] = useState([DATA_INSIGHTS, BIWEEKLY]) const isSubmittable = frequencies.length !== 0 const updateFrequencies = (e: React.ChangeEvent) => { @@ -98,17 +99,17 @@ export const NewsletterSubscriptionForm = ({
-
@@ -133,15 +134,15 @@ export const NewsletterSubscriptionForm = ({
Please select at least one option.
)}
-
- {/* This hidden field should not be the last element in the form as long as we use the row-gap mixin - to space elements vertically. When placed as the last element of the form, this hidden element becomes + {/* This hidden field should not be the last element in the form as long as we use the row-gap mixin + to space elements vertically. When placed as the last element of the form, this hidden element becomes the target of the :last-child selector of the row-gap mixin, when it should be applied to the last visible element instead */} { - const input = document.querySelector(".aa-Input") + const input = document.querySelector(".aa-TextInput") if (input) { input.focus() input.setAttribute("required", "true") diff --git a/site/css/newsletter-subscription.scss b/site/css/newsletter-subscription.scss index b156939e810..7ccb87d107c 100644 --- a/site/css/newsletter-subscription.scss +++ b/site/css/newsletter-subscription.scss @@ -33,16 +33,8 @@ } .NewsletterSubscription__email { - border: 1px solid $blue-20; - border-radius: 0; - height: inherit; - padding: 0 16px; flex: 1; width: 100%; - &::placeholder { - @include body-3-medium; - color: $blue-40; - } } .NewsletterSubscription__submit { diff --git a/site/gdocs/components/DataInsightsNewsletter.scss b/site/gdocs/components/DataInsightsNewsletter.scss new file mode 100644 index 00000000000..44ab2ab578c --- /dev/null +++ b/site/gdocs/components/DataInsightsNewsletter.scss @@ -0,0 +1,141 @@ +.data-insights-newsletter { + background-color: $blue-10; + padding-top: 32px; + padding-bottom: 16px; + + @include sm-only { + padding-top: 16px; + } +} + +.data-insights-newsletter__inner { + display: flex; + align-items: start; + justify-content: space-between; + gap: 16px 24px; + + @include sm-only { + flex-wrap: wrap; + } +} + +.data-insights-newsletter__close { + display: flex; + position: relative; + padding: 4px 0; + + @include sm-only { + padding: 0; + } +} + +.data-insights-newsletter__close-button { + position: absolute; + top: -32px; + right: -14px; + color: $blue-60; + cursor: pointer; + background: none; + border: none; + padding: 0; + margin-left: auto; + height: 40px; + width: 40px; + + &:hover { + color: $blue-90; + } +} + +.data-insights-newsletter--has-close .data-insights-newsletter__close-button { + @include sm-only { + top: -12px; + } +} + +.data-insights-newsletter__heading { + color: $blue-90; + margin: 0; +} + +.data-insights-newsletter__description { + color: $blue-65; + margin: 0; + + @include sm-only { + display: none; + } +} + +.data-insights-newsletter__left { + display: flex; + align-items: start; + gap: 16px; + + @include sm-only { + flex: auto; + align-items: center; + justify-content: space-between; + flex-direction: row-reverse; + } +} + +.data-insights-newsletter--has-close .data-insights-newsletter__left { + @include sm-only { + padding-right: 24px; + } +} + +.data-insights-newsletter__left__text { + flex: auto; + display: flex; + flex-direction: column; + gap: 4px; +} + +.data-insights-newsletter__icon { + flex-shrink: 0; +} + +.data-insights-newsletter--has-close .data-insights-newsletter__icon { + @include sm-only { + display: none; + } +} + +.data-insights-newsletter__right { + flex: auto; + display: flex; + flex-direction: column; +} + +.data-insights-newsletter__form { + flex: auto; + display: flex; + align-items: center; + justify-content: end; + gap: 8px; +} + +.data-insights-newsletter__email-input { + flex: 0 1 400px; + + @include sm-only { + flex: 1 1 100%; + } +} + +.data-insights-newsletter__note { + color: $blue-65; + margin: 8px 0 0 auto; + + a { + color: $blue-65; + text-decoration: underline; + text-underline-offset: 4px; + + &:hover { + text-decoration: none; + } + } +} diff --git a/site/gdocs/components/DataInsightsNewsletter.tsx b/site/gdocs/components/DataInsightsNewsletter.tsx new file mode 100644 index 00000000000..f2faaffa9bb --- /dev/null +++ b/site/gdocs/components/DataInsightsNewsletter.tsx @@ -0,0 +1,125 @@ +import * as React from "react" +import cx from "classnames" +import { faXmark } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { Button, TextInput } from "@ourworldindata/components" + +export default function DataInsightsNewsletter({ + className, + onClose, +}: { + className?: string + onClose?: () => void +}) { + return ( +
+ {onClose && ( +
+ +
+ )} +
+
+ +
+

+ Get Daily Data Insights delivered to your inbox +

+

+ Receive an email from us whenever we publish a Daily + Data Insight (maximum 1 per day). +

+
+
+
+
+ + +
+
+
+ ) +} + +function Icon({ className }: { className?: string }) { + return ( + + + + + + + + + + + ) +} diff --git a/site/gdocs/components/LatestDataInsights.scss b/site/gdocs/components/LatestDataInsights.scss index d8047dfba7b..24ec3bfc398 100644 --- a/site/gdocs/components/LatestDataInsights.scss +++ b/site/gdocs/components/LatestDataInsights.scss @@ -1,6 +1,10 @@ .latest-data-insights { position: relative; margin-bottom: 24px; + + @include md-down { + margin-bottom: 48px; + } } .latest-data-insights__viewport { diff --git a/site/gdocs/components/LatestDataInsightsBlock.tsx b/site/gdocs/components/LatestDataInsightsBlock.tsx index c7c6b954909..19619d8e514 100644 --- a/site/gdocs/components/LatestDataInsightsBlock.tsx +++ b/site/gdocs/components/LatestDataInsightsBlock.tsx @@ -4,6 +4,7 @@ import cx from "classnames" import { AttachmentsContext } from "../OwidGdoc.js" import { Button } from "@ourworldindata/components" import LatestDataInsights from "./LatestDataInsights.js" +import DataInsightsNewsletter from "./DataInsightsNewsletter.js" export default function LatestDataInsightsBlock({ className, @@ -32,6 +33,7 @@ export default function LatestDataInsightsBlock({ className="span-cols-12 col-start-2" latestDataInsights={latestDataInsights} /> + ) } diff --git a/site/gdocs/pages/DataInsight.tsx b/site/gdocs/pages/DataInsight.tsx index 83e776e32be..3b3e96ad53e 100644 --- a/site/gdocs/pages/DataInsight.tsx +++ b/site/gdocs/pages/DataInsight.tsx @@ -14,6 +14,7 @@ import LatestDataInsights, { } from "../components/LatestDataInsights.js" import { AttachmentsContext } from "../OwidGdoc.js" import { BAKED_BASE_URL } from "../../../settings/clientSettings.js" +import DataInsightsNewsletterBanner from "../../DataInsightsNewsletterBanner.js" export const MOST_RECENT_DATA_INSIGHT = "most-recent-data-insight" export const SECOND_MOST_RECENT_INSIGHT = "second-most-recent-data-insight" @@ -175,6 +176,7 @@ export const DataInsightPage = ( className="span-cols-12 col-start-2" latestDataInsights={latestDataInsights} /> + ) } diff --git a/site/gdocs/pages/Homepage.scss b/site/gdocs/pages/Homepage.scss index 4f3d6381100..a6bde2c9ad5 100644 --- a/site/gdocs/pages/Homepage.scss +++ b/site/gdocs/pages/Homepage.scss @@ -65,10 +65,6 @@ } .NewsletterSubscription__email { - border: 1px solid #dbe5f0; - border-radius: 0; - height: inherit; - padding: 0 16px; flex: 1; margin-right: 8px; width: 100%; diff --git a/site/owid.scss b/site/owid.scss index c162cbf1777..3d7540ca334 100644 --- a/site/owid.scss +++ b/site/owid.scss @@ -30,6 +30,7 @@ @import "../packages/@ourworldindata/components/src/IndicatorSources/IndicatorSources.scss"; @import "../packages/@ourworldindata/components/src/IndicatorProcessing/IndicatorProcessing.scss"; @import "../packages/@ourworldindata/components/src/Button/Button.scss"; +@import "../packages/@ourworldindata/components/src/TextInput"; @import "css/grid.scss"; @import "css/layout.scss"; @@ -82,6 +83,7 @@ @import "./gdocs/components/centered-article.scss"; @import "./gdocs/components/topic-page.scss"; @import "./gdocs/components/DataInsightDateline.scss"; +@import "./gdocs/components/DataInsightsNewsletter.scss"; @import "./gdocs/components/LatestDataInsights.scss"; @import "./gdocs/components/LatestDataInsightsBlock.scss"; @import "./gdocs/components/SDGGrid.scss"; @@ -97,6 +99,7 @@ @import "./gdocs/components/KeyIndicator.scss"; @import "./gdocs/components/KeyIndicatorCollection.scss"; @import "./gdocs/components/PillRow.scss"; +@import "./DataInsightsNewsletterBanner.scss"; @import "./DataPage.scss"; @import "./DataPageContent.scss"; @import "./GrapherImage.scss";