src/elements/callout/Callout.tsx (155 lines of code) (raw):

import { css } from "@emotion/react"; import { neutral, space } from "@guardian/src-foundations"; import { textSans } from "@guardian/src-foundations/typography"; import React, { useEffect, useState } from "react"; import { createCheckBoxField } from "../../plugin/fieldViews/CheckboxFieldView"; import { createCustomDropdownField, createCustomField, } from "../../plugin/fieldViews/CustomFieldView"; import { createRichTextField } from "../../plugin/fieldViews/RichTextFieldView"; import { createTextField } from "../../plugin/fieldViews/TextFieldView"; import { undefinedDropdownValue } from "../../plugin/helpers/constants"; import { dropDownRequired } from "../../plugin/helpers/validation"; import { createReactElementSpec } from "../../renderers/react/createReactElementSpec"; import { CustomDropdownView } from "../../renderers/react/customFieldViewComponents/CustomDropdownView"; import { CalloutError } from "./CalloutError"; import { CalloutTable } from "./CalloutTable"; import type { Campaign } from "./CalloutTypes"; const getDropdownOptionsFromCampaignList = (campaignList: Campaign[]) => { const campaigns = campaignList.map((campaign) => { const name = campaign.name.replace("CALLOUT:", "").trimStart(); return { text: name, value: campaign.id }; }); return [ { text: "Select from open callouts", value: undefinedDropdownValue }, ...campaigns, ]; }; export const calloutFields = { campaignId: createCustomDropdownField( undefinedDropdownValue, [], [ dropDownRequired( "A current campaign must be selected for the callout to appear", "WARN" ), ] ), isNonCollapsible: createCustomField(false, true), tagId: createTextField(), useDefaultPrompt: createCheckBoxField(true), overridePrompt: createTextField({ placeholder: "Don't show prompt", }), useDefaultTitle: createCheckBoxField(true), overrideTitle: createTextField({ placeholder: "Don't show title", }), useDefaultDescription: createCheckBoxField(true), overrideDescription: createRichTextField({ placeholder: "Don't show description", isResizeable: true, marks: "em strong link", }), }; const calloutStyles = css` ${textSans.small({ fontWeight: "regular", lineHeight: "loose" })} font-family: "Guardian Agate Sans"; code { font-family: monospace; background-color: ${neutral[86]}; border-radius: ${space[1]}px; padding: 1px 4px; } table { width: 100%; border-collapse: collapse; border: 1px solid ${neutral[86]}; font-size: 14px; } th, tr, td { border: 1px solid ${neutral[86]}; padding: ${space[1]}px; line-height: 14px; vertical-align: top; } th { text-align: left; } p, p:first-child { margin-top: 0px; margin-bottom: 0px; } ul { margin-bottom: 0px; } `; type Props = { fetchCampaignList: () => Promise<Campaign[]>; targetingUrl: string; }; export const createCalloutElement = ({ fetchCampaignList, targetingUrl, }: Props) => createReactElementSpec({ fieldDescriptions: calloutFields, component: ({ fields }) => { const campaignId = fields.campaignId.value; const [campaignList, setCampaignList] = useState<Campaign[]>([]); useEffect(() => { void fetchCampaignList().then((campaignList) => { setCampaignList(campaignList); }); }, []); const getTag = (id: string) => { const campaign = campaignList.find((campaign) => campaign.id === id); return campaign?.fields.tagName ?? ""; }; const dropdownOptions = getDropdownOptionsFromCampaignList(campaignList); const callout = campaignList.find( (campaign) => campaign.id === campaignId ); const isActiveCallout = callout?.activeUntil === undefined || callout.activeUntil >= Date.now(); const trimmedTargetingUrl = targetingUrl.replace(/\/$/, ""); return campaignId && campaignId != "none-selected" ? ( <div css={calloutStyles}> {callout && isActiveCallout ? ( <CalloutTable calloutData={callout} targetingUrl={trimmedTargetingUrl} isNonCollapsible={fields.isNonCollapsible} useDefaultPrompt={fields.useDefaultPrompt} overridePrompt={fields.overridePrompt} useDefaultTitle={fields.useDefaultTitle} overrideTitle={fields.overrideTitle} useDefaultDescription={fields.useDefaultDescription} overrideDescription={fields.overrideDescription} /> ) : ( <CalloutError isExpired={!isActiveCallout} targetingUrl={trimmedTargetingUrl} callout={callout} calloutId={campaignId} /> )} </div> ) : ( <div> <CustomDropdownView label="Callout" field={fields.campaignId} options={dropdownOptions} onChange={(value) => { const tagId = getTag(value); fields.tagId.update(tagId); }} /> </div> ); }, });