+
{__('Accordion Item Text')}
diff --git a/packages/drupal/gutenberg_blocks/js/blocks/accordion.tsx b/packages/drupal/gutenberg_blocks/js/blocks/accordion.tsx
new file mode 100644
index 000000000..ea934733b
--- /dev/null
+++ b/packages/drupal/gutenberg_blocks/js/blocks/accordion.tsx
@@ -0,0 +1,70 @@
+import { InnerBlocks, InspectorControls } from 'wordpress__block-editor';
+import { registerBlockType } from 'wordpress__blocks';
+import { PanelBody, SelectControl } from 'wordpress__components';
+
+const { t: __ } = Drupal;
+
+enum HeadingLevels {
+ H2 = 'h2',
+ H3 = 'h3',
+ H4 = 'h4',
+ H5 = 'h5',
+}
+
+registerBlockType('custom/accordion', {
+ title: __('Accordion'),
+ icon: 'menu',
+ category: 'layout',
+ attributes: {
+ headingLevel: {
+ type: 'string',
+ default: HeadingLevels.H2,
+ },
+ },
+ edit: (props) => {
+ const { setAttributes } = props;
+
+ return (
+ <>
+
+
+ {
+ setAttributes({ headingLevel });
+ }}
+ />
+
+
+
+
+ >
+ );
+ },
+ save: () =>
,
+});
diff --git a/packages/drupal/gutenberg_blocks/js/blocks/conditional.tsx b/packages/drupal/gutenberg_blocks/js/blocks/conditional.tsx
new file mode 100644
index 000000000..c282591df
--- /dev/null
+++ b/packages/drupal/gutenberg_blocks/js/blocks/conditional.tsx
@@ -0,0 +1,254 @@
+import clsx from 'clsx';
+import React, { PropsWithChildren } from 'react';
+import { InnerBlocks, InspectorControls } from 'wordpress__block-editor';
+import { registerBlockType } from 'wordpress__blocks';
+import {
+ BaseControl,
+ PanelBody,
+ PanelRow,
+ TextControl,
+} from 'wordpress__components';
+
+const { t: __ } = Drupal;
+
+type ConditionsType = {
+ [key: string]: {
+ label: string;
+ visible: boolean;
+ template: JSX.Element;
+ };
+};
+
+const blockTitle = __('Conditional content');
+
+registerBlockType(`custom/conditional`, {
+ title: blockTitle,
+ 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) || '';
+
+ // 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: ConditionsType = {
+ scheduledDisplay: {
+ label: '⏱️ ' + __('Scheduled display'),
+ visible: !!(displayFrom || displayTo),
+ template: (
+ <>
+ {displayFrom ? (
+ <>
+
{__('From')}:{' '}
+ {new Date(displayFrom).toLocaleString()}
+ >
+ ) : null}
+ {displayFrom && displayTo ? ' ' : null}
+ {displayTo ? (
+ <>
+
{__('To')}:{' '}
+ {new Date(displayTo).toLocaleString()}
+ >
+ ) : null}
+ >
+ ),
+ },
+ device: {
+ label: '📱 ' + __('Device'),
+ visible: false,
+ template: <>{'Mobile only.'}>,
+ },
+ };
+
+ const hasConditions = Object.values(conditions)
+ .filter(({ visible }) => visible)
+ .some(Boolean);
+
+ const conditionsSummary = hasConditions ? (
+ Object.entries(conditions)
+ .filter(([, value]) => !!value)
+ .filter(([, { visible }]) => visible)
+ .map(([, { label, template }]) => (
+ <>
+
{label}
+ {template}
+ >
+ ))
+ ) : (
+ <>{'ℹ️ ' + __('No conditions set')}>
+ );
+
+ return (
+ <>
+
+ {conditionsSummary}
+
+
+
+
+
+
+
+ setAttributes({ purpose: value })}
+ help={__(
+ 'The value is not exposed to the frontend and serves to identify the reason of the conditional content (e.g. Summer Campaign).',
+ )}
+ />
+
+
+
+
+
+ div]:flex [&>div]:gap-4 [&>div>label]:w-4 [&>div>label]:my-auto !m-0'
+ }
+ >
+ {
+ setAttributes({
+ displayFrom: event.target.value
+ ? localToIsoTime(event.target.value)
+ : '',
+ });
+ }}
+ />
+
+ div]:flex [&>div]:gap-4 [&>div>label]:w-4 [&>div>label]:mb-0 [&>div>label]:my-auto !m-0'
+ }
+ >
+ {
+ setAttributes({
+ displayTo: event.target.value
+ ? localToIsoTime(event.target.value)
+ : '',
+ });
+ }}
+ />
+
+
+
+
+ {__('Time zone') +
+ ': ' +
+ Intl.DateTimeFormat().resolvedOptions().timeZone}
+
+
+
+
+ >
+ );
+ },
+
+ save() {
+ return
;
+ },
+});
+
+const localToIsoTime = (localTime: string) => {
+ return new Date(localTime).toISOString();
+};
+
+const isoToLocalTime = (isoTime: string) => {
+ const date = new Date(isoTime);
+ date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
+ return date.toISOString().slice(0, 16);
+};
+
+const CollapsibleContainer = ({
+ children,
+ label,
+ title,
+ isActive,
+}: PropsWithChildren<{ label: string; title: string; isActive: boolean }>) => {
+ return (
+ <>
+
+
+
+
+
+ {title}
+
+
+
+
+ >
+ );
+};
diff --git a/packages/drupal/gutenberg_blocks/src/blocks/content.tsx b/packages/drupal/gutenberg_blocks/js/blocks/content.tsx
similarity index 97%
rename from packages/drupal/gutenberg_blocks/src/blocks/content.tsx
rename to packages/drupal/gutenberg_blocks/js/blocks/content.tsx
index 1aff9b0ac..d5aa26b2a 100644
--- a/packages/drupal/gutenberg_blocks/src/blocks/content.tsx
+++ b/packages/drupal/gutenberg_blocks/js/blocks/content.tsx
@@ -1,7 +1,6 @@
import { InnerBlocks } from 'wordpress__block-editor';
import { registerBlockType } from 'wordpress__blocks';
-// @ts-ignore
const { t: __ } = Drupal;
const style = {
diff --git a/packages/drupal/gutenberg_blocks/src/blocks/cta.tsx b/packages/drupal/gutenberg_blocks/js/blocks/cta.tsx
similarity index 99%
rename from packages/drupal/gutenberg_blocks/src/blocks/cta.tsx
rename to packages/drupal/gutenberg_blocks/js/blocks/cta.tsx
index 5b70375d3..b2f11af6b 100644
--- a/packages/drupal/gutenberg_blocks/src/blocks/cta.tsx
+++ b/packages/drupal/gutenberg_blocks/js/blocks/cta.tsx
@@ -9,10 +9,7 @@ import { registerBlockType } from 'wordpress__blocks';
import { PanelBody, SelectControl, ToggleControl } from 'wordpress__components';
import { compose, withState } from 'wordpress__compose';
-// @ts-ignore
const { t: __ } = Drupal;
-
-// @ts-ignore
const { setPlainTextAttribute } = silverbackGutenbergUtils;
const ArrowRightIcon = () => (
diff --git a/packages/drupal/gutenberg_blocks/src/blocks/demo-block.tsx b/packages/drupal/gutenberg_blocks/js/blocks/demo-block.tsx
similarity index 98%
rename from packages/drupal/gutenberg_blocks/src/blocks/demo-block.tsx
rename to packages/drupal/gutenberg_blocks/js/blocks/demo-block.tsx
index d35d6354d..4c0958f0b 100644
--- a/packages/drupal/gutenberg_blocks/src/blocks/demo-block.tsx
+++ b/packages/drupal/gutenberg_blocks/js/blocks/demo-block.tsx
@@ -7,8 +7,8 @@ import { dispatch } from 'wordpress__data';
import { DrupalMediaEntity } from '../utils/drupal-media';
-// @ts-ignore
const { t: __ } = Drupal;
+
// @ts-ignore
registerBlockType('custom/demo-block', {
title: 'Demo Block',
@@ -47,7 +47,7 @@ registerBlockType('custom/demo-block', {
Block settings
-
+
{__('Demo Block')}
string };
-
-declare const drupalSettings: {
- path: {
- baseUrl: string;
- pathPrefix: string;
- };
- customGutenbergBlocks: {
- forms: Array<{
- id: string;
- url: string;
- label: string;
- }>;
- };
-};
-
const { t: __ } = Drupal;
registerBlockType(`custom/form`, {
@@ -53,7 +37,7 @@ registerBlockType(`custom/form`, {
/>
-
+
{__('Form')}
{props.attributes.formId ? (