diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 61a708b..7f7aedd 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -5,6 +5,9 @@ on: branches: - main +env: + NODE_VERSION: 20.x + jobs: towncrier: runs-on: ubuntu-latest @@ -17,6 +20,30 @@ jobs: - name: Install towncrier run: pip install towncrier + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Enable corepack + run: corepack enable + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: make install + - uses: dorny/paths-filter@v2 id: filter with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d33b590..49e10ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,11 +26,24 @@ jobs: - run: npm install -g npm - - name: Install pnpm - uses: pnpm/action-setup@v3 + - name: Enable corepack + run: corepack enable + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache with: - run_install: | - - args: [--frozen-lockfile] + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: make install - name: Extract package from release tag id: get_package diff --git a/packages/volto-blocks/locales/en/LC_MESSAGES/volto.po b/packages/volto-blocks/locales/en/LC_MESSAGES/volto.po index 5d52915..825b24e 100644 --- a/packages/volto-blocks/locales/en/LC_MESSAGES/volto.po +++ b/packages/volto-blocks/locales/en/LC_MESSAGES/volto.po @@ -28,6 +28,9 @@ msgstr "" #. Default: "Text" #: components/blocks/Accordion/EditItem +#: components/blocks/IconsAndText/Edit +#: components/blocks/IconsAndText/EditItem +#: components/blocks/IconsAndText/schema #: components/blocks/Testimonials/Edit #: components/blocks/Testimonials/schema #: components/blocks/Text1/Edit @@ -37,10 +40,18 @@ msgstr "" msgid "Text" msgstr "" +#. Default: "Type header text..." +#: components/blocks/IconsAndText/EditItem +msgid "Text placeholder" +msgstr "" + #. Default: "Title" #: components/blocks/Accordion/Edit #: components/blocks/Accordion/EditItem #: components/blocks/Accordion/schema +#: components/blocks/IconsAndText/Edit +#: components/blocks/IconsAndText/EditItem +#: components/blocks/IconsAndText/schema #: components/blocks/Testimonials/Edit #: components/blocks/Testimonials/schema #: components/blocks/Text1/Edit @@ -52,6 +63,121 @@ msgstr "" msgid "Title" msgstr "" +#. Default: "Add column" +#: components/blocks/IconsAndText/schema +msgid "addColumn" +msgstr "" + +#. Default: "After title" +#: components/blocks/IconsAndText/schema +msgid "after_title" +msgstr "" + +#. Default: "Before header text" +#: components/blocks/IconsAndText/schema +msgid "before_header_text" +msgstr "" + +#. Default: "Before title" +#: components/blocks/IconsAndText/schema +msgid "before_title" +msgstr "" + +#. Default: "Column" +#: components/blocks/IconsAndText/schema +msgid "column" +msgstr "" + +#. Default: "Number of columns" +#: components/blocks/IconsAndText/schema +msgid "columnsNumber" +msgstr "" + +#. Default: "The image must be a PNG or SVG. The maximum recommended size for PNG is 200x200px." +#: components/blocks/IconsAndText/schema +msgid "description" +msgstr "" + +#. Default: "Divider position" +#: components/blocks/IconsAndText/schema +msgid "divider_position" +msgstr "" + +#. Default: "Header text position" +#: components/blocks/IconsAndText/schema +msgid "header_text_position" +msgstr "" + +#. Default: "Icon" +#: components/blocks/IconsAndText/schema +msgid "icon" +msgstr "" + +#. Default: "Define the number of columns for this block" +#: components/blocks/IconsAndText/schema +msgid "icon_and_text_column_number_description" +msgstr "" + +#. Default: "Column Number" +#: components/blocks/IconsAndText/schema +msgid "icon_and_text_column_number_title" +msgstr "" + +#. Default: "Icon & Text block" +#: components/blocks/IconsAndText/schema +msgid "icons_and_text_title" +msgstr "" + +#. Default: "Image size" +#: components/blocks/IconsAndText/schema +msgid "image_size" +msgstr "" + +#. Default: "Large" +#: components/blocks/IconsAndText/schema +msgid "large" +msgstr "" + +#. Default: "Link" +#: components/blocks/IconsAndText/schema +msgid "link" +msgstr "" + +#. Default: "Link title" +#: components/blocks/IconsAndText/schema +msgid "link_title" +msgstr "" + +#. Default: "If no title is entered, and a link is selected, the link will be added to the block title." +#: components/blocks/IconsAndText/schema +msgid "link_title_description" +msgstr "" + +#. Default: "Medium" +#: components/blocks/IconsAndText/schema +msgid "medium" +msgstr "" + +#. Default: "Do not fit the columns to the available space" +#: components/blocks/IconsAndText/schema +msgid "noAdaptColumns" +msgstr "" + +#. Default: "Hide divider" +#: components/blocks/IconsAndText/schema +msgid "no_divider" +msgstr "" + +#. Default: "On bottom" +#: components/blocks/IconsAndText/schema +msgid "on_bottom" +msgstr "" + +#. Default: "On right" +#: components/blocks/IconsAndText/schema +msgid "on_right" +msgstr "" + #. Default: "Columns" #: components/blocks/Accordion/schema msgid "redturtle__volto-blocks__accordion_columns_title" @@ -131,3 +257,8 @@ msgstr "" #: components/blocks/Text7/schema msgid "redturtle__volto-blocks__text7_title" msgstr "" + +#. Default: "Small" +#: components/blocks/IconsAndText/schema +msgid "small" +msgstr "" diff --git a/packages/volto-blocks/locales/it/LC_MESSAGES/volto.po b/packages/volto-blocks/locales/it/LC_MESSAGES/volto.po index 8330a77..cdca41b 100644 --- a/packages/volto-blocks/locales/it/LC_MESSAGES/volto.po +++ b/packages/volto-blocks/locales/it/LC_MESSAGES/volto.po @@ -28,6 +28,9 @@ msgstr "" #. Default: "Text" #: components/blocks/Accordion/EditItem +#: components/blocks/IconsAndText/Edit +#: components/blocks/IconsAndText/EditItem +#: components/blocks/IconsAndText/schema #: components/blocks/Testimonials/Edit #: components/blocks/Testimonials/schema #: components/blocks/Text1/Edit @@ -37,10 +40,18 @@ msgstr "" msgid "Text" msgstr "Testo" +#. Default: "Type header text..." +#: components/blocks/IconsAndText/EditItem +msgid "Text placeholder" +msgstr "" + #. Default: "Title" #: components/blocks/Accordion/Edit #: components/blocks/Accordion/EditItem #: components/blocks/Accordion/schema +#: components/blocks/IconsAndText/Edit +#: components/blocks/IconsAndText/EditItem +#: components/blocks/IconsAndText/schema #: components/blocks/Testimonials/Edit #: components/blocks/Testimonials/schema #: components/blocks/Text1/Edit @@ -52,6 +63,121 @@ msgstr "Testo" msgid "Title" msgstr "Titolo" +#. Default: "Add column" +#: components/blocks/IconsAndText/schema +msgid "addColumn" +msgstr "" + +#. Default: "After title" +#: components/blocks/IconsAndText/schema +msgid "after_title" +msgstr "" + +#. Default: "Before header text" +#: components/blocks/IconsAndText/schema +msgid "before_header_text" +msgstr "" + +#. Default: "Before title" +#: components/blocks/IconsAndText/schema +msgid "before_title" +msgstr "" + +#. Default: "Column" +#: components/blocks/IconsAndText/schema +msgid "column" +msgstr "" + +#. Default: "Number of columns" +#: components/blocks/IconsAndText/schema +msgid "columnsNumber" +msgstr "" + +#. Default: "The image must be a PNG or SVG. The maximum recommended size for PNG is 200x200px." +#: components/blocks/IconsAndText/schema +msgid "description" +msgstr "" + +#. Default: "Divider position" +#: components/blocks/IconsAndText/schema +msgid "divider_position" +msgstr "" + +#. Default: "Header text position" +#: components/blocks/IconsAndText/schema +msgid "header_text_position" +msgstr "" + +#. Default: "Icon" +#: components/blocks/IconsAndText/schema +msgid "icon" +msgstr "" + +#. Default: "Define the number of columns for this block" +#: components/blocks/IconsAndText/schema +msgid "icon_and_text_column_number_description" +msgstr "" + +#. Default: "Column Number" +#: components/blocks/IconsAndText/schema +msgid "icon_and_text_column_number_title" +msgstr "" + +#. Default: "Icon & Text block" +#: components/blocks/IconsAndText/schema +msgid "icons_and_text_title" +msgstr "" + +#. Default: "Image size" +#: components/blocks/IconsAndText/schema +msgid "image_size" +msgstr "" + +#. Default: "Large" +#: components/blocks/IconsAndText/schema +msgid "large" +msgstr "" + +#. Default: "Link" +#: components/blocks/IconsAndText/schema +msgid "link" +msgstr "" + +#. Default: "Link title" +#: components/blocks/IconsAndText/schema +msgid "link_title" +msgstr "" + +#. Default: "If no title is entered, and a link is selected, the link will be added to the block title." +#: components/blocks/IconsAndText/schema +msgid "link_title_description" +msgstr "" + +#. Default: "Medium" +#: components/blocks/IconsAndText/schema +msgid "medium" +msgstr "" + +#. Default: "Do not fit the columns to the available space" +#: components/blocks/IconsAndText/schema +msgid "noAdaptColumns" +msgstr "" + +#. Default: "Hide divider" +#: components/blocks/IconsAndText/schema +msgid "no_divider" +msgstr "" + +#. Default: "On bottom" +#: components/blocks/IconsAndText/schema +msgid "on_bottom" +msgstr "" + +#. Default: "On right" +#: components/blocks/IconsAndText/schema +msgid "on_right" +msgstr "" + #. Default: "Columns" #: components/blocks/Accordion/schema msgid "redturtle__volto-blocks__accordion_columns_title" @@ -131,3 +257,8 @@ msgstr "" #: components/blocks/Text7/schema msgid "redturtle__volto-blocks__text7_title" msgstr "" + +#. Default: "Small" +#: components/blocks/IconsAndText/schema +msgid "small" +msgstr "" diff --git a/packages/volto-blocks/locales/volto.pot b/packages/volto-blocks/locales/volto.pot index 4618aa8..a142053 100644 --- a/packages/volto-blocks/locales/volto.pot +++ b/packages/volto-blocks/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2024-08-20T08:35:27.767Z\n" +"POT-Creation-Date: 2024-09-04T09:44:50.487Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "Content-Type: text/plain; charset=utf-8\n" @@ -30,6 +30,9 @@ msgstr "" #. Default: "Text" #: components/blocks/Accordion/EditItem +#: components/blocks/IconsAndText/Edit +#: components/blocks/IconsAndText/EditItem +#: components/blocks/IconsAndText/schema #: components/blocks/Testimonials/Edit #: components/blocks/Testimonials/schema #: components/blocks/Text1/Edit @@ -39,10 +42,18 @@ msgstr "" msgid "Text" msgstr "" +#. Default: "Type header text..." +#: components/blocks/IconsAndText/EditItem +msgid "Text placeholder" +msgstr "" + #. Default: "Title" #: components/blocks/Accordion/Edit #: components/blocks/Accordion/EditItem #: components/blocks/Accordion/schema +#: components/blocks/IconsAndText/Edit +#: components/blocks/IconsAndText/EditItem +#: components/blocks/IconsAndText/schema #: components/blocks/Testimonials/Edit #: components/blocks/Testimonials/schema #: components/blocks/Text1/Edit @@ -54,6 +65,121 @@ msgstr "" msgid "Title" msgstr "" +#. Default: "Add column" +#: components/blocks/IconsAndText/schema +msgid "addColumn" +msgstr "" + +#. Default: "After title" +#: components/blocks/IconsAndText/schema +msgid "after_title" +msgstr "" + +#. Default: "Before header text" +#: components/blocks/IconsAndText/schema +msgid "before_header_text" +msgstr "" + +#. Default: "Before title" +#: components/blocks/IconsAndText/schema +msgid "before_title" +msgstr "" + +#. Default: "Column" +#: components/blocks/IconsAndText/schema +msgid "column" +msgstr "" + +#. Default: "Number of columns" +#: components/blocks/IconsAndText/schema +msgid "columnsNumber" +msgstr "" + +#. Default: "The image must be a PNG or SVG. The maximum recommended size for PNG is 200x200px." +#: components/blocks/IconsAndText/schema +msgid "description" +msgstr "" + +#. Default: "Divider position" +#: components/blocks/IconsAndText/schema +msgid "divider_position" +msgstr "" + +#. Default: "Header text position" +#: components/blocks/IconsAndText/schema +msgid "header_text_position" +msgstr "" + +#. Default: "Icon" +#: components/blocks/IconsAndText/schema +msgid "icon" +msgstr "" + +#. Default: "Define the number of columns for this block" +#: components/blocks/IconsAndText/schema +msgid "icon_and_text_column_number_description" +msgstr "" + +#. Default: "Column Number" +#: components/blocks/IconsAndText/schema +msgid "icon_and_text_column_number_title" +msgstr "" + +#. Default: "Icon & Text block" +#: components/blocks/IconsAndText/schema +msgid "icons_and_text_title" +msgstr "" + +#. Default: "Image size" +#: components/blocks/IconsAndText/schema +msgid "image_size" +msgstr "" + +#. Default: "Large" +#: components/blocks/IconsAndText/schema +msgid "large" +msgstr "" + +#. Default: "Link" +#: components/blocks/IconsAndText/schema +msgid "link" +msgstr "" + +#. Default: "Link title" +#: components/blocks/IconsAndText/schema +msgid "link_title" +msgstr "" + +#. Default: "If no title is entered, and a link is selected, the link will be added to the block title." +#: components/blocks/IconsAndText/schema +msgid "link_title_description" +msgstr "" + +#. Default: "Medium" +#: components/blocks/IconsAndText/schema +msgid "medium" +msgstr "" + +#. Default: "Do not fit the columns to the available space" +#: components/blocks/IconsAndText/schema +msgid "noAdaptColumns" +msgstr "" + +#. Default: "Hide divider" +#: components/blocks/IconsAndText/schema +msgid "no_divider" +msgstr "" + +#. Default: "On bottom" +#: components/blocks/IconsAndText/schema +msgid "on_bottom" +msgstr "" + +#. Default: "On right" +#: components/blocks/IconsAndText/schema +msgid "on_right" +msgstr "" + #. Default: "Columns" #: components/blocks/Accordion/schema msgid "redturtle__volto-blocks__accordion_columns_title" @@ -133,3 +259,8 @@ msgstr "" #: components/blocks/Text7/schema msgid "redturtle__volto-blocks__text7_title" msgstr "" + +#. Default: "Small" +#: components/blocks/IconsAndText/schema +msgid "small" +msgstr "" diff --git a/packages/volto-blocks/news/58009.feature b/packages/volto-blocks/news/58009.feature new file mode 100644 index 0000000..ea7f4dd --- /dev/null +++ b/packages/volto-blocks/news/58009.feature @@ -0,0 +1 @@ +Added Icons and Text block @luca-bellenghi diff --git a/packages/volto-blocks/package.json b/packages/volto-blocks/package.json index 6c21dfb..c09f193 100644 --- a/packages/volto-blocks/package.json +++ b/packages/volto-blocks/package.json @@ -54,6 +54,7 @@ "classnames": "2.2.6", "lodash": "4.17.21", "react-aria-components": "^1.2.0", - "react-intl": "3.12.1" + "react-intl": "3.12.1", + "uuid": "^10.0.0" } } diff --git a/packages/volto-blocks/src/components/blocks/IconsAndText/Edit.tsx b/packages/volto-blocks/src/components/blocks/IconsAndText/Edit.tsx new file mode 100644 index 0000000..ae30e41 --- /dev/null +++ b/packages/volto-blocks/src/components/blocks/IconsAndText/Edit.tsx @@ -0,0 +1,173 @@ +import type { BlockEditProps } from '@plone/types'; +import { Icon, SidebarPortal, UniversalLink } from '@plone/volto/components'; +import { BlockDataForm } from '@plone/volto/components/manage/Form'; +import EditItem from '@redturtle/volto-blocks/components/blocks/IconsAndText/EditItem'; +import type { IconsAndTextData } from '@redturtle/volto-blocks/components/blocks/IconsAndText/schema'; +import styles from '@redturtle/volto-blocks/components/blocks/IconsAndText/styles.module.scss'; +import blockIcon from '@redturtle/volto-blocks/icons/icons_and_text.svg'; +import { + TextEditorWidget, + useHandleDetachedBlockFocus, +} from '@redturtle/volto-rt-slate'; +import cx from 'classnames'; +import React, { useEffect } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { v4 as uuid } from 'uuid'; + +import config from '@plone/registry'; + +type IconsAndTextEditProps = Omit & { + data: IconsAndTextData; +}; + +export default function Edit(props: IconsAndTextEditProps) { + const { data, selected, block, onChangeBlock, blocksConfig, blocksErrors } = + props; + const intl = useIntl(); + + const column_number = data.column_number ? parseInt(data.column_number) : 3; + + const { selectedField, setSelectedField } = useHandleDetachedBlockFocus( + props, + 'title', + ); + + useEffect(() => { + if (!data?.columns || data?.columns?.length === 0) { + onChangeBlock(block, { + ...data, + columns: [ + { '@id': uuid(), dividerPosition: 'before_title', iconSize: 's' }, + { '@id': uuid(), dividerPosition: 'before_title', iconSize: 's' }, + { '@id': uuid(), dividerPosition: 'before_title', iconSize: 's' }, + ], + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [block]); + + const schema = blocksConfig[data['@type']].blockSchema({ + data, + intl, + }); + + if (__SERVER__) { + return
; + } + + const Container = config.getComponent('Container').component || 'div'; + + return ( + <> +
+ +
+ { + setSelectedField('text'); + }} + showToolbar={false} + placeholder={intl.formatMessage(messages.title)} + /> + { + setSelectedField('title'); + }} + placeholder={intl.formatMessage(messages.text)} + /> +
+
+ {data.columns?.length > 0 && + data.columns.map((item, i) => ( + { + onChangeBlock(block, { + ...data, + columns: data.columns.map((i) => + i['@id'] === id ? { ...i, [field]: value } : i, + ), + }); + }} + // @ts-expect-error TODO fix type in @plone/types + selected={selected} + /> + ))} +
+ {data.linkHref?.[0] && ( +
+ ) => { + e.preventDefault(); + }} + > + {data.linkTitle} + +
+ )} +
+
+ {/* @ts-expect-error TODO */} + + {schema && ( + } + schema={schema} + title={schema.title} + onChangeField={(id: string, value: unknown) => { + onChangeBlock(block, { + ...data, + [id]: value, + }); + }} + onChangeBlock={onChangeBlock} + formData={data} + block={block} + errors={blocksErrors} + /> + )} + + + ); +} + +const messages = defineMessages({ + title: { + id: 'Title', + defaultMessage: 'Title...', + }, + text: { + id: 'Text', + defaultMessage: 'Type text...', + }, +}); diff --git a/packages/volto-blocks/src/components/blocks/IconsAndText/EditItem.tsx b/packages/volto-blocks/src/components/blocks/IconsAndText/EditItem.tsx new file mode 100644 index 0000000..a8717ef --- /dev/null +++ b/packages/volto-blocks/src/components/blocks/IconsAndText/EditItem.tsx @@ -0,0 +1,140 @@ +import config from '@plone/registry'; +import type { ArrayElement } from '@plone/types'; +import { UniversalLink } from '@plone/volto/components'; +import { flattenToAppURL } from '@plone/volto/helpers'; +import type { IconsAndTextData } from '@redturtle/volto-blocks/components/blocks/IconsAndText/schema'; +import styles from '@redturtle/volto-blocks/components/blocks/IconsAndText/styles.module.scss'; +import { TextEditorWidget } from '@redturtle/volto-rt-slate'; +import cx from 'classnames'; +import { defineMessages, useIntl } from 'react-intl'; +type Props = { + data: ArrayElement; + focusOn: string; + setFocusOn: Function; + onChange: (id: string, field: string, value: unknown) => void; + selected: boolean; +}; + +export default function EditItem({ + data, + focusOn, + setFocusOn, + onChange, + selected, +}: Props) { + const intl = useIntl(); + const icon = data.iconImage; + const Image = config.getComponent('Image').component; + return ( +
+
+ {icon && ( +
+ +
+ )} + + {data.headerTextPosition && ( +
+ setFocusOn('headerText' + data['@id'])} + onChangeBlock={(id: string, value: { headerText: string }) => { + onChange(id, 'headerText', value.headerText); + }} + showToolbar={false} + placeholder={intl.formatMessage(messages.textPlaceholder)} + /> +
+ )} +
+ setFocusOn('title' + data['@id'])} + onChangeBlock={(id: string, value: { title: string }) => { + onChange(id, 'title', value.title); + }} + showToolbar={false} + placeholder={intl.formatMessage(messages.title)} + /> + setFocusOn('text' + data['@id'])} + onChangeBlock={(id: string, value: { text: string }) => { + onChange(id, 'text', value.text); + }} + placeholder={intl.formatMessage(messages.text)} + /> + {data.href_title && data.href?.length > 0 && ( +
+
+ ) => { + e.preventDefault(); + }} + > + {data.href_title} + +
+
+ )} +
+ ); +} + +const messages = defineMessages({ + title: { + id: 'Title', + defaultMessage: 'Title...', + }, + text: { + id: 'Text', + defaultMessage: 'Type Text...', + }, + textPlaceholder: { + id: 'Text placeholder', + defaultMessage: 'Type header text...', + }, +}); diff --git a/packages/volto-blocks/src/components/blocks/IconsAndText/View.tsx b/packages/volto-blocks/src/components/blocks/IconsAndText/View.tsx new file mode 100644 index 0000000..db291b7 --- /dev/null +++ b/packages/volto-blocks/src/components/blocks/IconsAndText/View.tsx @@ -0,0 +1,71 @@ +import config from '@plone/registry'; +import type { BlockViewProps } from '@plone/types'; +import { TextBlockView } from '@plone/volto-slate/blocks/Text'; +import { UniversalLink } from '@plone/volto/components'; +import ViewItem from '@redturtle/volto-blocks/components/blocks/IconsAndText/ViewItem'; +import type { IconsAndTextData } from '@redturtle/volto-blocks/components/blocks/IconsAndText/schema'; +import styles from '@redturtle/volto-blocks/components/blocks/IconsAndText/styles.module.scss'; +import cx from 'classnames'; + +type Props = Omit & { + data: IconsAndTextData; +}; + +export default function View({ data, className, style }: Props) { + const column_number = data.column_number ? parseInt(data.column_number) : 3; + + const Container = config.getComponent('Container').component || 'div'; + + return ( + <> +
+ +
+ {data.title && ( +

+ {data.title} +

+ )} + {data.text && ( +
+ +
+ )} +
+
+ {data.columns?.length > 0 && + data.columns.map((item, i) => ( + + ))} +
+ {data.linkHref?.[0] && ( +
+ + {data.linkTitle} + +
+ )} +
+
+ + ); +} diff --git a/packages/volto-blocks/src/components/blocks/IconsAndText/ViewItem.tsx b/packages/volto-blocks/src/components/blocks/IconsAndText/ViewItem.tsx new file mode 100644 index 0000000..ab98bca --- /dev/null +++ b/packages/volto-blocks/src/components/blocks/IconsAndText/ViewItem.tsx @@ -0,0 +1,90 @@ +import config from '@plone/registry'; +import type { ArrayElement } from '@plone/types'; +import { TextBlockView } from '@plone/volto-slate/blocks/Text'; +import { UniversalLink } from '@plone/volto/components'; +import { flattenToAppURL } from '@plone/volto/helpers'; +import type { IconsAndTextData } from '@redturtle/volto-blocks/components/blocks/IconsAndText/schema'; +import styles from '@redturtle/volto-blocks/components/blocks/IconsAndText/styles.module.scss'; +import cx from 'classnames'; + +type Props = { + data: ArrayElement; +}; + +export default function ViewItem({ data }: Props) { + const icon = data.iconImage; + const Image = config.getComponent('Image').component; + return ( +
+
+ {icon && ( +
+ +
+ )} + {data.headerTextPosition && ( +
+ {data.title && ( +

+ {data.headerText} +

+ )} +
+ )} +
+ {data.title && ( +
+ {data.title && ( +

+ {data.title} +

+ )} +
+ )} + {data.text && ( +
+ +
+ )} + {data.href_title && data.href?.length > 0 && ( +
+
+ + {data.href_title} + +
+
+ )} +
+ ); +} diff --git a/packages/volto-blocks/src/components/blocks/IconsAndText/index.ts b/packages/volto-blocks/src/components/blocks/IconsAndText/index.ts new file mode 100644 index 0000000..fbc7268 --- /dev/null +++ b/packages/volto-blocks/src/components/blocks/IconsAndText/index.ts @@ -0,0 +1,5 @@ +import loadable from '@loadable/component'; + +export const IconsAndTextEdit = loadable( + () => import(/* webpackChunkName: "redturtle__volto-blocks" */ './Edit'), +); diff --git a/packages/volto-blocks/src/components/blocks/IconsAndText/schema.ts b/packages/volto-blocks/src/components/blocks/IconsAndText/schema.ts new file mode 100644 index 0000000..7a82f95 --- /dev/null +++ b/packages/volto-blocks/src/components/blocks/IconsAndText/schema.ts @@ -0,0 +1,224 @@ +import type { BlockConfigBase, JSONSchema } from '@plone/types'; +import { + addCtaFieldset, + type CtaBlockExtender, +} from '@redturtle/volto-blocks/components/blocks/extenders/cta'; + +import { defineMessages, type IntlShape } from 'react-intl'; +// import { v4 as uuid } from 'uuid'; + +const messages = defineMessages({ + columnsNumber: { id: 'columnsNumber', defaultMessage: 'Number of columns' }, + noAdaptColumns: { + id: 'noAdaptColumns', + defaultMessage: 'Do not fit the columns to the available space', + }, + column: { + id: 'column', + defaultMessage: 'Column', + }, + addColumn: { + id: 'addColumn', + defaultMessage: 'Add column', + }, + icon: { + id: 'icon', + defaultMessage: 'Icon', + }, + icon_and_text_column_number_title: { + id: 'icon_and_text_column_number_title', + defaultMessage: 'Column Number', + }, + icon_and_text_column_number_description: { + id: 'icon_and_text_column_number_description', + defaultMessage: 'Define the number of columns for this block', + }, + title_title: { + id: 'Title', + defaultMessage: 'Title', + }, + text_title: { + id: 'Text', + defaultMessage: 'Text', + }, + icons_and_text_title: { + id: 'icons_and_text_title', + defaultMessage: 'Icon & Text block', + }, + description: { + id: 'description', + defaultMessage: + 'The image must be a PNG or SVG. The maximum recommended size for PNG is 200x200px.', + }, + image_size: { id: 'image_size', defaultMessage: 'Image size' }, + small: { id: 'small', defaultMessage: 'Small' }, + medium: { id: 'medium', defaultMessage: 'Medium' }, + large: { id: 'large', defaultMessage: 'Large' }, + header_text_position: { + id: 'header_text_position', + defaultMessage: 'Header text position', + }, + on_right: { id: 'on_right', defaultMessage: 'On right' }, + on_bottom: { id: 'on_bottom', defaultMessage: 'On bottom' }, + divider_position: { + id: 'divider_position', + defaultMessage: 'Divider position', + }, + before_title: { id: 'before_title', defaultMessage: 'Before title' }, + after_title: { id: 'after_title', defaultMessage: 'After title' }, + before_header_text: { + id: 'before_header_text', + defaultMessage: 'Before header text', + }, + no_divider: { id: 'no_divider', defaultMessage: 'Hide divider' }, + link: { id: 'link', defaultMessage: 'Link' }, + link_title: { id: 'link_title', defaultMessage: 'Link title' }, + link_title_description: { + id: 'link_title_description', + defaultMessage: + 'If no title is entered, and a link is selected, the link will be added to the block title.', + }, +}); + +export interface IconsAndTextData extends CtaBlockExtender { + '@type': 'iconsandtext'; + columns?: Array<{ + '@id': string; + index?: number; + iconImage?: string; + iconSize?: string; + headerText?: string; + headerTextPosition?: string; + dividerPosition?: string; + href?: string; + href_title?: string; + title?: string; + text?: string; + }>; + column_number?: string; + title?: string; + text?: object; +} + +export const IconsAndTextSchema = ({ + data, + intl, +}: { + data: IconsAndTextData; + intl: IntlShape; +}): JSONSchema => { + const schema = { + title: intl.formatMessage(messages.icons_and_text_title), + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: ['title', 'columns', 'column_number'], + }, + ], + properties: { + title: { title: intl.formatMessage(messages.title_title) }, + columns: { + title: intl.formatMessage(messages.column), + addMessage: intl.formatMessage(messages.addColumn), + widget: 'object_list', + // default: [ + // { '@id': uuid(), dividerPosition: 'before_title', iconSize: 's' }, + // { '@id': uuid(), dividerPosition: 'before_title', iconSize: 's' }, + // { '@id': uuid(), dividerPosition: 'before_title', iconSize: 's' }, + // ], + schema: { + title: intl.formatMessage(messages.column), + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [ + // 'title', + 'iconImage', + 'iconSize', + 'headerTextPosition', + 'dividerPosition', + 'href', + 'href_title', + ], + }, + ], + properties: { + title: { title: intl.formatMessage(messages.column) }, + iconImage: { + title: intl.formatMessage(messages.icon), + description: intl.formatMessage(messages.description), + widget: 'image', + }, + iconSize: { + title: intl.formatMessage(messages.image_size), + type: 'choices', + choices: [ + ['s', intl.formatMessage(messages.small)], + ['m', intl.formatMessage(messages.medium)], + ['l', intl.formatMessage(messages.large)], + ], + noValueOption: false, + }, + headerTextPosition: { + title: intl.formatMessage(messages.header_text_position), + type: 'choices', + choices: [ + ['right', intl.formatMessage(messages.on_right)], + ['bottom', intl.formatMessage(messages.on_bottom)], + ], + }, + dividerPosition: { + title: intl.formatMessage(messages.divider_position), + type: 'choices', + default: 'no_divider', + choices: [ + ['before_title', intl.formatMessage(messages.before_title)], + ['after_title', intl.formatMessage(messages.after_title)], + [ + 'before_header_text', + intl.formatMessage(messages.before_header_text), + ], + ['no_divider', intl.formatMessage(messages.no_divider)], + ], + + noValueOption: false, + }, + href: { + title: intl.formatMessage(messages.link), + widget: 'object_browser', + allowExternals: true, + mode: 'link', + }, + href_title: { + title: intl.formatMessage(messages.link_title), + description: intl.formatMessage(messages.link_title_description), + }, + }, + required: [], + }, + }, + column_number: { + title: intl.formatMessage(messages.icon_and_text_column_number_title), + description: intl.formatMessage( + messages.icon_and_text_column_number_description, + ), + default: '3', + choices: [ + ['2', '2'], + ['3', '3'], + ['4', '4'], + ], + }, + }, + required: ['title'], + }; + addCtaFieldset({ schema, intl }); + return schema; +}; + +export interface IconsAndTextConfig + extends Omit { + blockSchema: typeof IconsAndTextSchema; +} diff --git a/packages/volto-blocks/src/components/blocks/IconsAndText/styles.module.scss b/packages/volto-blocks/src/components/blocks/IconsAndText/styles.module.scss new file mode 100644 index 0000000..d2ff2f8 --- /dev/null +++ b/packages/volto-blocks/src/components/blocks/IconsAndText/styles.module.scss @@ -0,0 +1,141 @@ +// Titolo e descrizione del blocco centrato +.block-content-header { + align-items: center; + margin-bottom: 2rem; + text-align: center; +} + +// Gestione titolo a destra o sotto immagine dell'header +.header-text-right { + .header-text { + margin-left: 1.2rem; + } +} + +.header-text-bottom { + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; +} + +.header-text { + width: 100%; + margin-top: 0.85rem; +} + +// Separatore (linea grigia) prima del titolo, dopo il titolo o prima dell'header +.divider_before_title { + .column-head { + border-bottom: 1px solid #ccc; + } +} + +.divider_after_title { + h4 { + padding-bottom: 0.5rem; + border-bottom: 1px solid #ccc; + margin-bottom: 0.5rem; + } +} + +.divider_before_header_text { + .column-head { + padding-top: 0.5rem; + border-bottom: 1px solid #ccc; + } +} + +// Gestione delle colonne del blocco +.column-number-4 { + .column-block { + flex-basis: 25%; + } +} + +.column-number-3 { + .column-block { + flex-basis: 33.33%; + } +} + +.column-number-2 { + .column-block { + flex-basis: 50%; + } +} + +// Gestione del blocco con icone e testo + +.block_icons_text { + position: relative; + display: flex; + flex-direction: column; + padding-top: 0.5rem; + + // Gestione dimensione immagine di testata + .column_icon { + /* size di default */ + img { + min-height: 2.7rem; + max-height: 2.7rem; + } + + &.size_s { + img { + min-height: 2.7rem; + max-height: 2.7rem; + } + } + + &.size_m { + img { + min-height: 5rem; + max-height: 5rem; + } + } + + &.size_l { + img { + min-height: 8rem; + max-height: 8rem; + } + + img[src*='png'], + img[src*='jpg'], + img[src*='jpeg'], + img[src*='gif'] { + width: 100%; + min-height: unset; + max-height: unset; + aspect-ratio: var(--grid-images-aspect-ratio, 1.77777778); + object-fit: cover; + object-position: var(--grid-images-object-position, top left); + } + } + } + + .block-columns-wrapper, + .columns-wrapper { + display: flex; + flex-wrap: wrap; + margin-right: -1.15rem; + margin-left: -1.15rem; + + .column-block { + position: relative; + display: flex; + flex: 1 1 25%; + flex-direction: column; + padding: 1.15rem; + margin-bottom: 2rem; + font-size: 1rem; + + .column-head { + display: flex; + align-items: center; + padding-bottom: 0.85rem; + margin-bottom: 0.85rem; + } + } + } +} diff --git a/packages/volto-blocks/src/config/blocks.ts b/packages/volto-blocks/src/config/blocks.ts index fada3ec..b3bb51b 100644 --- a/packages/volto-blocks/src/config/blocks.ts +++ b/packages/volto-blocks/src/config/blocks.ts @@ -3,43 +3,51 @@ import type { BlocksConfigData } from '@plone/types'; import BlockSettingsSchema from '@plone/volto/components/manage/Blocks/Block/Schema'; import accordionSVG from '@plone/volto/icons/list-arrows.svg'; -import AccordionView from '@redturtle/volto-blocks/components/blocks/Accordion/View'; import { AccordionEdit } from '@redturtle/volto-blocks/components/blocks/Accordion'; import { AccordionSchema, type AccordionConfig, } from '@redturtle/volto-blocks/components/blocks/Accordion/schema'; +import AccordionView from '@redturtle/volto-blocks/components/blocks/Accordion/View'; -import text1SVG from '@redturtle/volto-blocks/icons/text1.svg'; -import Text1View from '@redturtle/volto-blocks/components/blocks/Text1/View'; import { Text1Edit } from '@redturtle/volto-blocks/components/blocks/Text1'; import { Text1Schema, type Text1Config, } from '@redturtle/volto-blocks/components/blocks/Text1/schema'; +import Text1View from '@redturtle/volto-blocks/components/blocks/Text1/View'; +import text1SVG from '@redturtle/volto-blocks/icons/text1.svg'; -import text6SVG from '@redturtle/volto-blocks/icons/text6.svg'; -import Text6View from '@redturtle/volto-blocks/components/blocks/Text6/View'; import { Text6Edit } from '@redturtle/volto-blocks/components/blocks/Text6'; import { Text6Schema, type Text6Config, } from '@redturtle/volto-blocks/components/blocks/Text6/schema'; +import Text6View from '@redturtle/volto-blocks/components/blocks/Text6/View'; +import text6SVG from '@redturtle/volto-blocks/icons/text6.svg'; -import text7SVG from '@redturtle/volto-blocks/icons/text7.svg'; -import Text7View from '@redturtle/volto-blocks/components/blocks/Text7/View'; import { Text7Edit } from '@redturtle/volto-blocks/components/blocks/Text7'; import { Text7Schema, type Text7Config, } from '@redturtle/volto-blocks/components/blocks/Text7/schema'; +import Text7View from '@redturtle/volto-blocks/components/blocks/Text7/View'; +import text7SVG from '@redturtle/volto-blocks/icons/text7.svg'; -import TestimonialsView from '@redturtle/volto-blocks/components/blocks/Testimonials/View'; import { TestimonialsEdit } from '@redturtle/volto-blocks/components/blocks/Testimonials'; import { TestimonialsSchema, type TestimonialsConfig, } from '@redturtle/volto-blocks/components/blocks/Testimonials/schema'; +import TestimonialsView from '@redturtle/volto-blocks/components/blocks/Testimonials/View'; + +import { IconsAndTextEdit } from '@redturtle/volto-blocks/components/blocks/IconsAndText'; +import { + IconsAndTextSchema, + type IconsAndTextConfig, +} from '@redturtle/volto-blocks/components/blocks/IconsAndText/schema'; +import IconsAndTextView from '@redturtle/volto-blocks/components/blocks/IconsAndText/View'; +import iconsAndTextSVG from '@redturtle/volto-blocks/icons/icons_and_text.svg'; declare module '@plone/types' { interface BlocksConfigData { @@ -48,12 +56,13 @@ declare module '@plone/types' { text6: Text6Config; text7: Text7Config; testimonials: TestimonialsConfig; + iconsandtext: IconsAndTextConfig; } } type RtBlocksConfig = Pick< BlocksConfigData, - 'accordion' | 'text1' | 'text6' | 'text7' | 'testimonials' + 'accordion' | 'text1' | 'text6' | 'text7' | 'testimonials' | 'iconsandtext' >; const defaultBlocksConfig = { @@ -124,4 +133,16 @@ export const blocks: RtBlocksConfig = { blockHasOwnFocusManagement: true, blockSchema: TestimonialsSchema, }, + iconsandtext: { + ...defaultBlocksConfig, + id: 'iconsandtext', + title: 'Icon & Text', + icon: iconsAndTextSVG, + group: 'text', + mostUsed: true, + view: IconsAndTextView, + edit: IconsAndTextEdit, + blockHasOwnFocusManagement: true, + blockSchema: IconsAndTextSchema, + }, } as const; diff --git a/packages/volto-blocks/src/declaration.d.ts b/packages/volto-blocks/src/declaration.d.ts index 7f95283..9dbb955 100644 --- a/packages/volto-blocks/src/declaration.d.ts +++ b/packages/volto-blocks/src/declaration.d.ts @@ -1,2 +1,8 @@ declare var __CLIENT__: boolean; declare var __SERVER__: boolean; +declare module '*.module.scss' { + let styles: { + readonly [key: string]: string; + }; + export default styles; +} diff --git a/packages/volto-blocks/src/icons/icons_and_text.svg b/packages/volto-blocks/src/icons/icons_and_text.svg new file mode 100644 index 0000000..8393bbf --- /dev/null +++ b/packages/volto-blocks/src/icons/icons_and_text.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6110b1d..0084cb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1476,6 +1476,9 @@ importers: react-intl: specifier: 3.12.1 version: 3.12.1(react@18.2.0) + uuid: + specifier: ^10.0.0 + version: 10.0.0 devDependencies: '@plone/scripts': specifier: ^3.6.1 @@ -14917,6 +14920,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. @@ -35085,6 +35092,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + uuid@3.4.0: {} uuid@7.0.3: {}