From 527a3356e9988e3689ddc982e3ef049ff238c08e Mon Sep 17 00:00:00 2001 From: Sergey <148218517+qradle-yndx@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:47:21 +0300 Subject: [PATCH] feat: add PriceCard (#697) * feat: add price-card * feat: made content-list a separate component --- .../ContentList/ContentList.scss | 20 ++- .../ContentList/ContentList.tsx | 32 ++-- .../ContentList/ContentListItemIcon.tsx | 33 ++++ .../ContentList/__stories__/ContentList.mdx | 16 ++ .../__stories__/ContentList.stories.tsx | 37 +++++ .../ContentList/__stories__/data.json | 33 ++++ src/components/FileLink/FileLink.tsx | 2 +- src/components/Link/Link.tsx | 1 - src/components/index.ts | 1 + src/models/constructor-items/blocks.ts | 10 +- src/models/constructor-items/sub-blocks.ts | 14 ++ src/schema/validators/sub-blocks.ts | 1 + src/sub-blocks/Content/Content.tsx | 5 +- .../Content/__stories__/Content.mdx | 5 +- src/sub-blocks/Content/__stories__/data.json | 12 +- src/sub-blocks/PriceCard/PriceCard.scss | 108 ++++++++++++ src/sub-blocks/PriceCard/PriceCard.tsx | 78 +++++++++ .../__stories__/PriceCard.stories.tsx | 43 +++++ .../PriceCard/__stories__/data.json | 154 ++++++++++++++++++ src/sub-blocks/PriceCard/schema.ts | 53 ++++++ src/sub-blocks/index.ts | 1 + src/text-transform/config.ts | 11 ++ 22 files changed, 632 insertions(+), 38 deletions(-) rename src/{sub-blocks/Content => components}/ContentList/ContentList.scss (72%) rename src/{sub-blocks/Content => components}/ContentList/ContentList.tsx (63%) create mode 100644 src/components/ContentList/ContentListItemIcon.tsx create mode 100644 src/components/ContentList/__stories__/ContentList.mdx create mode 100644 src/components/ContentList/__stories__/ContentList.stories.tsx create mode 100644 src/components/ContentList/__stories__/data.json create mode 100644 src/sub-blocks/PriceCard/PriceCard.scss create mode 100644 src/sub-blocks/PriceCard/PriceCard.tsx create mode 100644 src/sub-blocks/PriceCard/__stories__/PriceCard.stories.tsx create mode 100644 src/sub-blocks/PriceCard/__stories__/data.json create mode 100644 src/sub-blocks/PriceCard/schema.ts diff --git a/src/sub-blocks/Content/ContentList/ContentList.scss b/src/components/ContentList/ContentList.scss similarity index 72% rename from src/sub-blocks/Content/ContentList/ContentList.scss rename to src/components/ContentList/ContentList.scss index 07198655b..31bd05340 100644 --- a/src/sub-blocks/Content/ContentList/ContentList.scss +++ b/src/components/ContentList/ContentList.scss @@ -1,9 +1,10 @@ -@import '../../../../styles/variables.scss'; -@import '../../../../styles/mixins.scss'; +@import '../../../styles/variables.scss'; +@import '../../../styles/mixins.scss'; $block: '.#{$ns}content-list'; $iconSizeL: 22px; $iconSizeS: 20px; +$iconSizeXS: 18px; $marginIconSizeS: 2px; $marginIconSizeL: 1px; @@ -23,6 +24,14 @@ $marginIconSizeL: 1px; margin-top: $marginIconSizeL; margin-bottom: $marginIconSizeL; margin-right: $indentXXS; + + &_without_title { + width: $iconSizeS; + height: $iconSizeS; + margin-top: 0; + margin-bottom: 0; + margin-right: $indentXXXS; + } } #{$block}__item { @@ -47,6 +56,13 @@ $marginIconSizeL: 1px; margin-top: $marginIconSizeS; margin-bottom: $marginIconSizeS; margin-right: $indentXXXS; + + &_without_title { + width: $iconSizeXS; + height: $iconSizeXS; + margin-top: 0; + margin-bottom: 0; + } } #{$block}__text { diff --git a/src/sub-blocks/Content/ContentList/ContentList.tsx b/src/components/ContentList/ContentList.tsx similarity index 63% rename from src/sub-blocks/Content/ContentList/ContentList.tsx rename to src/components/ContentList/ContentList.tsx index d5aec8f8a..bacb37680 100644 --- a/src/sub-blocks/Content/ContentList/ContentList.tsx +++ b/src/components/ContentList/ContentList.tsx @@ -2,14 +2,13 @@ import React from 'react'; import {v4 as uuidv4} from 'uuid'; -import {YFMWrapper} from '../../../components'; -import Image from '../../../components/Image/Image'; -import {getMediaImage} from '../../../components/Media/Image/utils'; -import {useTheme} from '../../../context/theme'; -import {ContentItemProps, ContentSize} from '../../../models'; -import {QAProps} from '../../../models/common'; -import {block, getThemedValue} from '../../../utils'; -import {getQaAttrubutes} from '../../../utils/blocks'; +import {ContentListProps, ContentSize} from '../../models'; +import {QAProps} from '../../models/common'; +import {block} from '../../utils'; +import {getQaAttrubutes} from '../../utils/blocks'; +import YFMWrapper from '../YFMWrapper/YFMWrapper'; + +import ItemIcon from './ContentListItemIcon'; import './ContentList.scss'; @@ -25,25 +24,20 @@ function getHeadingLevel(size: ContentSize) { } } -interface ContentListProps { - list: ContentItemProps[]; - size: ContentSize; -} - -const ContentList = ({list, size, qa}: ContentListProps & QAProps) => { - const theme = useTheme(); +const ContentList = ({list, size = 'l', qa}: ContentListProps & QAProps) => { const qaAttributes = getQaAttrubutes(qa, ['image', 'title', 'text']); return (
{list?.map((item) => { const {icon, title, text} = item; - const iconThemed = getThemedValue(icon, theme); - const iconData = getMediaImage(iconThemed); - return (
- +
{title && React.createElement( diff --git a/src/components/ContentList/ContentListItemIcon.tsx b/src/components/ContentList/ContentListItemIcon.tsx new file mode 100644 index 000000000..2b54797cf --- /dev/null +++ b/src/components/ContentList/ContentListItemIcon.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import {useTheme} from '../../context/theme'; +import {ClassNameProps, ImageProps, QAProps, SVGIcon} from '../../models'; +import {ThemeSupporting, getThemedValue} from '../../utils'; +import Image from '../Image/Image'; +import {getMediaImage} from '../Media/Image/utils'; + +interface ListItemProps extends QAProps, ClassNameProps { + icon: ThemeSupporting; +} + +function isIconSvg(icon: ImageProps | SVGIcon): icon is SVGIcon { + return typeof icon === 'function'; +} + +const ContentListItemIcon = ({icon, className, qa}: ListItemProps) => { + const theme = useTheme(); + const iconThemed = getThemedValue(icon, theme); + + if (isIconSvg(iconThemed)) { + const Icon = iconThemed; + return ( +
+ +
+ ); + } + const iconData = getMediaImage(iconThemed); + return ; +}; + +export default ContentListItemIcon; diff --git a/src/components/ContentList/__stories__/ContentList.mdx b/src/components/ContentList/__stories__/ContentList.mdx new file mode 100644 index 000000000..706e67a22 --- /dev/null +++ b/src/components/ContentList/__stories__/ContentList.mdx @@ -0,0 +1,16 @@ +import {Meta} from '@storybook/blocks'; + +import {StoryTemplate} from '../../../demo/StoryTemplate.mdx'; +import * as ContentListStories from './ContentList.stories.tsx'; + + + +## Parameters + +- `size?: 's' | 'l'` — Component's size that defines font sizes ('l' by default) + +`list: Array` - An Array of items with icon +- [`icon: string | ImageObjectProps | ReactNode` — Icon](?path=/docs/documentation-types--docs#imageobjectprops---image-property). +- `title?: string` — Title. +- `text?: string` — Text (with YFM support) + diff --git a/src/components/ContentList/__stories__/ContentList.stories.tsx b/src/components/ContentList/__stories__/ContentList.stories.tsx new file mode 100644 index 000000000..efbfbb4e1 --- /dev/null +++ b/src/components/ContentList/__stories__/ContentList.stories.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +import {Meta, StoryFn} from '@storybook/react'; + +import {yfmTransform} from '../../../../.storybook/utils'; +import {ContentItemProps, ContentListProps} from '../../../models'; +import ContentList from '../ContentList'; + +import data from './data.json'; + +const transformList = (item: ContentItemProps) => ({ + ...item, + text: item?.text && yfmTransform(item.text), +}); + +export default { + args: {list: data.default.content.map(transformList), size: 'l'}, + component: ContentList, + title: 'Components/ContentList', +} as Meta; + +const DefaultTemplate: StoryFn = (args) => ( +
+ +
+); + +const DifferentSizeTemplate: StoryFn = (args) => ( +
+

Size L

+ +

Size S

+ +
+); +export const Default = DefaultTemplate.bind({}); +export const Size = DifferentSizeTemplate.bind({}); diff --git a/src/components/ContentList/__stories__/data.json b/src/components/ContentList/__stories__/data.json new file mode 100644 index 000000000..5d381edf1 --- /dev/null +++ b/src/components/ContentList/__stories__/data.json @@ -0,0 +1,33 @@ +{ + "centered": { + "content": { + "centered": true + } + }, + "default": { + "content": [ + { + "icon": { + "light": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_1_light.svg", + "dark": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_1_dark.svg" + }, + "title": "Default", + "text": "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + }, + { + "icon": { + "light": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_3_light.svg", + "dark": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_3_dark.svg" + }, + "title": "With title only" + }, + { + "icon": { + "light": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_2_light.svg", + "dark": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_2_dark.svg" + }, + "text": "With text only. **Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + } + ] + } +} diff --git a/src/components/FileLink/FileLink.tsx b/src/components/FileLink/FileLink.tsx index 970f00448..1796a1f4e 100644 --- a/src/components/FileLink/FileLink.tsx +++ b/src/components/FileLink/FileLink.tsx @@ -22,7 +22,7 @@ export enum FileExtension { } export function getFileExt(name: string) { - if (name.includes(FIGMA_URL)) { + if (name?.includes(FIGMA_URL)) { return FileExtension.FIG; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx index 9d772549d..aa493be96 100644 --- a/src/components/Link/Link.tsx +++ b/src/components/Link/Link.tsx @@ -134,7 +134,6 @@ const LinkBlock = (props: WithChildren) => { return null; } }; - return (
{getLinkByType()}
); diff --git a/src/components/index.ts b/src/components/index.ts index cd2b64d72..b1b4e5410 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -34,6 +34,7 @@ export {default as RouterLink} from './RouterLink/RouterLink'; export {default as HTML} from './HTML/HTML'; export {default as MetaInfo} from './MetaInfo/MetaInfo'; export {default as FullscreenMedia} from './FullscreenMedia/FullscreenMedia'; +export {default as ContentList} from './ContentList/ContentList'; export type {RouterLinkProps} from './RouterLink/RouterLink'; export type {ImageBaseProps} from './ImageBase/ImageBase'; diff --git a/src/models/constructor-items/blocks.ts b/src/models/constructor-items/blocks.ts index cd7465447..68646c866 100644 --- a/src/models/constructor-items/blocks.ts +++ b/src/models/constructor-items/blocks.ts @@ -20,6 +20,7 @@ import { HeaderOffset, HeaderWidth, ImageDeviceProps, + ImageProps, Justify, LegendTableMarkerType, LinkProps, @@ -348,10 +349,17 @@ export interface ContentLayoutBlockProps extends ContentLayoutBlockParams { fileContent?: FileLinkProps[]; } +export type SVGIcon = React.FC>; + export interface ContentItemProps { title?: string; text?: string; - icon: ThemedImage; + icon: ThemeSupporting; +} + +export interface ContentListProps { + list: ContentItemProps[]; + size: ContentSize; } export interface ContentBlockProps { diff --git a/src/models/constructor-items/sub-blocks.ts b/src/models/constructor-items/sub-blocks.ts index 3d82d78d3..a099cde82 100644 --- a/src/models/constructor-items/sub-blocks.ts +++ b/src/models/constructor-items/sub-blocks.ts @@ -13,6 +13,7 @@ import { DividerSize, ImageObjectProps, ImageProps, + LinkProps, MediaProps, PriceDetailedProps, TextTheme, @@ -38,6 +39,7 @@ export enum SubBlockType { * @deprecated Will be removed, use BasicCard instead */ Card = 'card', + PriceCard = 'price-card', } export enum IconPosition { @@ -134,6 +136,18 @@ export interface BannerCardProps { export interface MediaCardProps extends MediaProps, AnalyticsEventsBase, CardBaseProps {} +export interface PriceCardProps extends CardBaseProps, Pick { + title: string; + price: string; + pricePeriod?: string; + priceDetails?: string; + description?: string; + buttons?: ButtonProps[]; + links?: LinkProps[]; + backgroundColor?: string; + list?: string[]; +} + export interface LayoutItemProps extends ClassNameProps, AnalyticsEventsBase { content: Omit; media: MediaProps; diff --git a/src/schema/validators/sub-blocks.ts b/src/schema/validators/sub-blocks.ts index dc34f953d..a24aae245 100644 --- a/src/schema/validators/sub-blocks.ts +++ b/src/schema/validators/sub-blocks.ts @@ -6,3 +6,4 @@ export * from '../../sub-blocks/LayoutItem/schema'; export * from '../../sub-blocks/Quote/schema'; export * from '../../sub-blocks/Divider/schema'; export * from '../../sub-blocks/BasicCard/schema'; +export * from '../../sub-blocks/PriceCard/schema'; diff --git a/src/sub-blocks/Content/Content.tsx b/src/sub-blocks/Content/Content.tsx index bc64177e0..36d528508 100644 --- a/src/sub-blocks/Content/Content.tsx +++ b/src/sub-blocks/Content/Content.tsx @@ -2,16 +2,13 @@ import React from 'react'; import {useUniqId} from '@gravity-ui/uikit'; -import {Button, Title, YFMWrapper} from '../../components'; -import LinkBlock from '../../components/Link/Link'; +import {Button, ContentList, Link as LinkBlock, Title, YFMWrapper} from '../../components'; import {Col} from '../../grid'; import {ClassNameProps, ContentBlockProps, ContentSize, TitleItemProps} from '../../models'; import {QAProps} from '../../models/common'; import {block} from '../../utils'; import {getQaAttrubutes} from '../../utils/blocks'; -import ContentList from './ContentList/ContentList'; - import './Content.scss'; const b = block('content'); diff --git a/src/sub-blocks/Content/__stories__/Content.mdx b/src/sub-blocks/Content/__stories__/Content.mdx index a940472dc..e94ac6a2c 100644 --- a/src/sub-blocks/Content/__stories__/Content.mdx +++ b/src/sub-blocks/Content/__stories__/Content.mdx @@ -31,8 +31,5 @@ import * as ContentStories from './Content.stories.tsx'; - `lg: number` — On a screen wider than 1081px. - `xl: number` — On a screen wider than 1185px. -`list: Array` - An Array of items with icon -- [`icon: string | ImageObjectProps` — Icon](?path=/docs/documentation-types--docs#imageobjectprops---image-property). -- `title?: string` — Title. -- `text?: string` — Text (with YFM support) +`list: Array` - An Array of items with icon - [ContentList](?path=/story/components-contentlist--docs&viewMode=docs) diff --git a/src/sub-blocks/Content/__stories__/data.json b/src/sub-blocks/Content/__stories__/data.json index 2bd6a2867..6b0fbf57c 100644 --- a/src/sub-blocks/Content/__stories__/data.json +++ b/src/sub-blocks/Content/__stories__/data.json @@ -40,17 +40,17 @@ }, { "icon": { - "light": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_2_light.svg", - "dark": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_2_dark.svg" + "light": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_3_light.svg", + "dark": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_3_dark.svg" }, - "text": "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + "title": "Lorem ipsum ipsum" }, { "icon": { - "light": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_3_light.svg", - "dark": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_3_dark.svg" + "light": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_2_light.svg", + "dark": "https://storage.yandexcloud.net/cloud-www-assets/constructor/storybook/images/icon_2_dark.svg" }, - "title": "Lorem ipsum ipsum" + "text": "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." } ] } diff --git a/src/sub-blocks/PriceCard/PriceCard.scss b/src/sub-blocks/PriceCard/PriceCard.scss new file mode 100644 index 000000000..2440dde5a --- /dev/null +++ b/src/sub-blocks/PriceCard/PriceCard.scss @@ -0,0 +1,108 @@ +@import '../../../styles/variables.scss'; +@import '../../../styles/mixins'; + +$block: '.#{$ns}price-card'; + +#{$block} { + position: relative; + + &__background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + &__content { + height: 100%; + display: flex; + flex-direction: column; + position: relative; + + &_theme { + &_dark { + color: var(--g-color-text-light-primary); + --g-color-line-focus: var(--pc-color-line-focus-dark); + + #{$block}__info .yfm, + #{$block}__info .yfm * { + color: var(--g-color-text-light-primary); + } + + #{$block}__info a { + text-decoration: underline; + + &:hover { + color: var(--g-color-text-light-secondary); + } + } + } + + &_light { + color: var(--g-color-text-dark-primary); + + #{$block}__info .yfm, + #{$block}__info .yfm * { + color: var(--g-color-text-dark-primary); + } + + #{$block}__info a { + text-decoration: underline; + + &:hover { + color: var(--g-color-text-dark-secondary); + } + } + } + } + } + + &__title { + font-size: var(--g-text-subheader-3-font-size); + line-height: var(--g-text-subheader-3-line-height); + margin-bottom: $indentSM; + } + + &__price { + margin-bottom: $indentSM; + + &-value { + font-size: var(--g-text-display-1-font-size); + line-height: var(--g-text-display-1-line-height); + } + + &-period { + margin-left: 4px; + } + + &-details { + margin-bottom: $indentXXS; + } + } + + &__main { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + position: relative; + } + + &__info { + margin-bottom: $indentSM; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + } + + &__links, + &__buttons { + & > * { + &:not(:last-child) { + margin-right: $indentXS; + } + } + } +} diff --git a/src/sub-blocks/PriceCard/PriceCard.tsx b/src/sub-blocks/PriceCard/PriceCard.tsx new file mode 100644 index 000000000..0d161c074 --- /dev/null +++ b/src/sub-blocks/PriceCard/PriceCard.tsx @@ -0,0 +1,78 @@ +import React from 'react'; + +import Check from '@gravity-ui/icons/Check'; + +import {BackgroundImage, Button, CardBase, ContentList, Link as LinkBlock} from '../../components'; +import {PriceCardProps} from '../../models'; +import {block} from '../../utils'; + +import './PriceCard.scss'; + +const b = block('price-card'); + +const PriceCard = (props: PriceCardProps) => { + const { + border, + title, + price, + pricePeriod, + priceDetails, + theme, + description, + list, + buttons, + links, + backgroundColor, + } = props; + return ( + + + +
+
+
{title}
+
+
+ {price} + {pricePeriod && ( + / {pricePeriod} + )} +
+ {priceDetails && ( +
{priceDetails}
+ )} +
+
{description}
+ {list?.length ? ( +
+ ({ + icon: Check, + text: item, + }))} + size="s" + /> +
+ ) : null} +
+ {buttons && ( +
+ {buttons.map((button) => ( +
+ )} + {links && ( +
+ {links.map((link) => ( + + ))} +
+ )} +
+
+
+ ); +}; + +export default PriceCard; diff --git a/src/sub-blocks/PriceCard/__stories__/PriceCard.stories.tsx b/src/sub-blocks/PriceCard/__stories__/PriceCard.stories.tsx new file mode 100644 index 000000000..bb0f787fe --- /dev/null +++ b/src/sub-blocks/PriceCard/__stories__/PriceCard.stories.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import {Meta, StoryFn} from '@storybook/react'; + +import {yfmTransform} from '../../../../.storybook/utils'; +import {PriceCardProps} from '../../../models'; +import PriceCard from '../PriceCard'; + +import data from './data.json'; + +export default { + component: PriceCard, + title: 'Components/Cards/PriceCard', +} as Meta; + +const DefaultTemplate: StoryFn = (args) => ( +
+ yfmTransform(text)) || undefined} /> +
+); + +const MultipleItemsTemplate: StoryFn<{items: PriceCardProps[]}> = ({items}) => ( +
+ {items.map((args, index) => ( +
+ yfmTransform(text)) || undefined} + /> +
+ ))} +
+); + +export const Default = DefaultTemplate.bind({}); +export const Link = DefaultTemplate.bind({}); +export const DifferentContent = MultipleItemsTemplate.bind({}); +export const Themed = MultipleItemsTemplate.bind({}); + +Default.args = data.default.content as PriceCardProps; +Link.args = data.link.content as PriceCardProps; +DifferentContent.args = {items: data.differentContent.content as PriceCardProps[]}; +Themed.args = {items: data.themed.content as PriceCardProps[]}; diff --git a/src/sub-blocks/PriceCard/__stories__/data.json b/src/sub-blocks/PriceCard/__stories__/data.json new file mode 100644 index 000000000..bcb47546f --- /dev/null +++ b/src/sub-blocks/PriceCard/__stories__/data.json @@ -0,0 +1,154 @@ +{ + "default": { + "content": { + "title": "Lorem ipsum", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "list": [ + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ], + "buttons": [ + { + "text": "Button", + "url": "https://example.com", + "width": "max", + "theme": "action" + } + ] + } + }, + "link": { + "content": { + "title": "Lorem ipsum", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "list": [ + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ], + "links": [ + { + "url": "#", + "text": "Link", + "theme": "normal", + "arrow": true + } + ] + } + }, + "differentContent": { + "content": [ + { + "title": "Lorem ipsum", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "list": [ + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ], + "buttons": [ + { + "text": "Button", + "url": "https://example.com", + "width": "max", + "theme": "action" + } + ] + }, + { + "title": "Lorem ipsum", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "buttons": [ + { + "text": "Button", + "url": "https://example.com", + "width": "max", + "theme": "outlined" + } + ] + }, + { + "title": "Lorem ipsum", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + } + ] + }, + "themed": { + "content": [ + { + "title": "Default theme", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "list": [ + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ], + "buttons": [ + { + "text": "Button", + "url": "https://example.com", + "width": "max", + "theme": "action" + } + ] + }, + { + "title": "Light theme", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "list": [ + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ], + "theme": "light", + "buttons": [ + { + "text": "Button", + "url": "https://example.com", + "width": "max", + "theme": "monochrome" + } + ], + "backgroundColor": "#CCDAFF" + }, + { + "title": "Dark theme", + "price": "299.99 $", + "pricePeriod": "month", + "priceDetails": "plan details", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "list": [ + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + "**Ut enim ad minim veniam** [quis nostrud](https://example.com) exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ], + "theme": "dark", + "buttons": [ + { + "text": "Button", + "url": "https://example.com", + "width": "max", + "theme": "normal-contrast" + } + ], + "backgroundColor": "#262626" + } + ] + } +} diff --git a/src/sub-blocks/PriceCard/schema.ts b/src/sub-blocks/PriceCard/schema.ts new file mode 100644 index 000000000..d1015babb --- /dev/null +++ b/src/sub-blocks/PriceCard/schema.ts @@ -0,0 +1,53 @@ +import { + AnimatableProps, + BaseProps, + ButtonBlock, + CardBase, + LinkProps, + MediaProps, +} from '../../schema/validators/common'; + +export const PriceCardBlock = { + 'price-card': { + additionalProperties: false, + required: ['title', 'price'], + properties: { + ...BaseProps, + ...CardBase, + ...MediaProps, + ...AnimatableProps, + title: { + type: 'string', + }, + price: { + type: 'string', + }, + pricePeriod: { + type: 'string', + }, + priceDetails: { + type: 'string', + }, + description: { + type: 'string', + }, + buttons: { + type: 'array', + items: ButtonBlock, + }, + links: { + type: 'array', + items: LinkProps, + }, + backgroundColor: { + type: 'string', + }, + list: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + }, +}; diff --git a/src/sub-blocks/index.ts b/src/sub-blocks/index.ts index 0d60e4d4c..eae436465 100644 --- a/src/sub-blocks/index.ts +++ b/src/sub-blocks/index.ts @@ -8,3 +8,4 @@ export {default as BackgroundCard} from './BackgroundCard/BackgroundCard'; export {default as BasicCard} from './BasicCard/BasicCard'; export {default as Content} from './Content/Content'; export {default as HubspotForm} from './HubspotForm'; +export {default as PriceCard} from './PriceCard/PriceCard'; diff --git a/src/text-transform/config.ts b/src/text-transform/config.ts index 07611d7b1..e5d88c284 100644 --- a/src/text-transform/config.ts +++ b/src/text-transform/config.ts @@ -363,4 +363,15 @@ export const config: BlocksConfig = { [BlockType.CardLayoutBlock]: blockHeaderTransformer, [BlockType.FilterBlock]: blockHeaderTransformer, [BlockType.IconsBlock]: blockHeaderTransformer, + [SubBlockType.PriceCard]: [ + { + fields: ['title'], + transformer: typografTransformer, + }, + { + fields: ['list'], + transformer: yfmTransformer, + parser: createItemsParser(['text']), + }, + ], };