-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(SLB-451): conditional content block
- Loading branch information
Showing
9 changed files
with
393 additions
and
24 deletions.
There are no files selected for viewing
171 changes: 171 additions & 0 deletions
171
packages/drupal/gutenberg_blocks/src/blocks/conditional.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import clsx from 'clsx'; | ||
import { InnerBlocks, InspectorControls } from 'wordpress__block-editor'; | ||
import { registerBlockType } from 'wordpress__blocks'; | ||
import { BaseControl, PanelBody } from 'wordpress__components'; | ||
|
||
// @ts-ignore | ||
const { t: __ } = Drupal; | ||
|
||
registerBlockType(`custom/conditional`, { | ||
title: __('Conditional content'), | ||
category: 'layout', | ||
icon: 'category', | ||
// Allow the block only at the root level to avoid GraphQL fragment recursion. | ||
parent: ['custom/content'], | ||
attributes: { | ||
displayFrom: { | ||
type: 'string', | ||
default: '', | ||
}, | ||
displayTo: { | ||
type: 'string', | ||
default: '', | ||
}, | ||
purpose: { | ||
type: 'string', | ||
default: '', | ||
}, | ||
}, | ||
edit(props) { | ||
const { attributes, setAttributes } = props; | ||
|
||
const displayFrom = attributes.displayFrom as string | undefined; | ||
const displayTo = attributes.displayTo as string | undefined; | ||
const purpose = ((attributes.purpose as string) || '').trim(); | ||
|
||
// Same logic as in BlockConditional.tsx | ||
const active = { | ||
scheduledDisplay: [ | ||
displayFrom | ||
? new Date(displayFrom).getTime() <= new Date().getTime() | ||
: true, | ||
displayTo ? new Date(displayTo).getTime() > new Date().getTime() : true, | ||
].every(Boolean), | ||
}; | ||
const isActive = Object.values(active).every(Boolean); | ||
|
||
const conditions = { | ||
scheduledDisplay: | ||
displayFrom || displayTo | ||
? '🕒 ' + | ||
__('Scheduled display') + | ||
': ' + | ||
[ | ||
displayFrom | ||
? __('From') + ' ' + new Date(displayFrom).toLocaleString() | ||
: '', | ||
displayTo | ||
? __('To') + ' ' + new Date(displayTo).toLocaleString() | ||
: '', | ||
] | ||
.filter(Boolean) | ||
.join(' ') | ||
: '', | ||
}; | ||
const hasConditions = Object.values(conditions).some(Boolean); | ||
const summary = hasConditions ? ( | ||
Object.entries(conditions) | ||
.filter(([, value]) => !!value) | ||
.map(([key, value]) => <div key={key}>{value}</div>) | ||
) : ( | ||
<div>{'ℹ️ ' + __('No conditions set')}</div> | ||
); | ||
|
||
return ( | ||
<div className={clsx('container-wrapper', { 'bg-gray-100': !isActive })}> | ||
<div className={'container-label'}>{__('Conditional content')}</div> | ||
<div className="text-sm text-gray-500">{summary}</div> | ||
<details open={isActive}> | ||
<summary | ||
style={{ cursor: 'pointer', padding: 0 }} | ||
className="text-sm text-gray-500" | ||
> | ||
{purpose || __('Content')} | ||
</summary> | ||
<InnerBlocks | ||
templateLock={false} | ||
template={[['core/paragraph', {}]]} | ||
/> | ||
</details> | ||
<InspectorControls> | ||
<PanelBody title={__('Purpose')}> | ||
<BaseControl id="purpose"> | ||
<input | ||
type="text" | ||
id="purpose" | ||
defaultValue={purpose} | ||
onChange={(event) => { | ||
setAttributes({ purpose: event.target.value }); | ||
}} | ||
/> | ||
</BaseControl> | ||
<BaseControl | ||
id="purpose-decription" | ||
label={__( | ||
'The value is not exposed to the frontend and serves to identify the reason of the conditional content (e.g. Summer Campaign).', | ||
)} | ||
> | ||
<div /> | ||
</BaseControl> | ||
</PanelBody> | ||
<PanelBody title={__('Scheduled display')}> | ||
<BaseControl id="displayFrom" label={__('From')}> | ||
<br /> | ||
<input | ||
type="datetime-local" | ||
id="displayFrom" | ||
defaultValue={displayFrom ? isoToLocalTime(displayFrom) : ''} | ||
onChange={(event) => { | ||
setAttributes({ | ||
displayFrom: event.target.value | ||
? localToIsoTime(event.target.value) | ||
: '', | ||
}); | ||
}} | ||
/> | ||
</BaseControl> | ||
<BaseControl id="displayTo" label={__('To')}> | ||
<br /> | ||
<input | ||
type="datetime-local" | ||
id="displayTo" | ||
defaultValue={displayTo ? isoToLocalTime(displayTo) : ''} | ||
onChange={(event) => { | ||
setAttributes({ | ||
displayTo: event.target.value | ||
? localToIsoTime(event.target.value) | ||
: '', | ||
}); | ||
}} | ||
/> | ||
</BaseControl> | ||
<BaseControl | ||
id="decription" | ||
label={ | ||
__('Time zone') + | ||
': ' + | ||
Intl.DateTimeFormat().resolvedOptions().timeZone | ||
} | ||
> | ||
<div /> | ||
</BaseControl> | ||
</PanelBody> | ||
</InspectorControls> | ||
</div> | ||
); | ||
}, | ||
|
||
save() { | ||
return <InnerBlocks.Content />; | ||
}, | ||
}); | ||
|
||
function localToIsoTime(localTime: string) { | ||
return new Date(localTime).toISOString(); | ||
} | ||
|
||
function isoToLocalTime(isoTime: string) { | ||
const date = new Date(isoTime); | ||
date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); | ||
return date.toISOString().slice(0, 16); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
packages/drupal/test_content/content/node/52ee5cc7-0ac5-49b5-8550-ce59476bd4ac.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
_meta: | ||
version: '1.0' | ||
entity_type: node | ||
uuid: 52ee5cc7-0ac5-49b5-8550-ce59476bd4ac | ||
bundle: page | ||
default_langcode: en | ||
default: | ||
revision_uid: | ||
- | ||
target_id: 1 | ||
status: | ||
- | ||
value: true | ||
uid: | ||
- | ||
target_id: 1 | ||
title: | ||
- | ||
value: 'Conditional blocks' | ||
created: | ||
- | ||
value: 1715684657 | ||
promote: | ||
- | ||
value: false | ||
sticky: | ||
- | ||
value: false | ||
moderation_state: | ||
- | ||
value: published | ||
path: | ||
- | ||
alias: /conditional-blocks | ||
langcode: en | ||
pathauto: 0 | ||
content_translation_source: | ||
- | ||
value: und | ||
content_translation_outdated: | ||
- | ||
value: false | ||
body: | ||
- | ||
value: |- | ||
<!-- wp:custom/hero /--> | ||
<!-- wp:custom/content --> | ||
<!-- wp:custom/conditional {"displayFrom":"2024-05-16T11:05:00.000Z","displayTo":"2024-05-23T13:03:00.000Z","purpose":"Summer Campaign"} --> | ||
<!-- wp:paragraph --> | ||
<p>Complete</p> | ||
<!-- /wp:paragraph --> | ||
<!-- /wp:custom/conditional --> | ||
<!-- wp:custom/conditional --> | ||
<!-- wp:paragraph --> | ||
<p>No conditions</p> | ||
<!-- /wp:paragraph --> | ||
<!-- /wp:custom/conditional --> | ||
<!-- /wp:custom/content --> | ||
format: gutenberg | ||
summary: '' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
packages/schema/src/fragments/PageContent/BlockConditional.gql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
fragment BlockConditional on BlockConditional { | ||
displayFrom | ||
displayTo | ||
content { | ||
__typename | ||
...BlockMarkup | ||
...BlockMedia | ||
...BlockForm | ||
...BlockImageTeasers | ||
...BlockCta | ||
...BlockImageWithText | ||
...BlockQuote | ||
...BlockHorizontalSeparator | ||
...BlockAccordion | ||
...BlockInfoGrid | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
packages/ui/src/components/Organisms/PageContent/BlockConditional.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { BlockConditionalFragment } from '@custom/schema'; | ||
import React, { useEffect, useState } from 'react'; | ||
|
||
import { isTruthy } from '../../../utils/isTruthy'; | ||
import { CommonContent } from '../PageDisplay'; | ||
|
||
export function BlockConditional(props: BlockConditionalFragment) { | ||
const [isActive, setIsActive] = useState(false); | ||
useEffect(() => { | ||
const active = { | ||
scheduledDisplay: [ | ||
props.displayFrom | ||
? new Date(props.displayFrom).getTime() <= new Date().getTime() | ||
: true, | ||
props.displayTo | ||
? new Date(props.displayTo).getTime() > new Date().getTime() | ||
: true, | ||
].every(Boolean), | ||
}; | ||
setIsActive(Object.values(active).every(Boolean)); | ||
}, []); | ||
|
||
return isActive ? ( | ||
<> | ||
{props.content?.filter(isTruthy).map((block, index) => { | ||
return <CommonContent key={index} {...block} />; | ||
})} | ||
</> | ||
) : null; | ||
} |
Oops, something went wrong.