Basic
Basic product
$19/month
Best offer for small companies
- Free Editor
- 1000 emails limit
- 10GB inbox
Most popular
Standard
Pro product
$39/month
Great choice for companies winth more than 10 employees
- 5000 emails limit
- 100GB inbox
- Next super feature
- Custom integrations
Pro
Expert product
$79/month
No limits! Best option for huge companies
- Unlimited inboxes
- Unlimited emails
- Unlimited space
Basic
Basic product
$19/month
Best offer for small companies
- Free Editor
- 1000 emails limit
- 10GB inbox
Most popular
Standard
Economical product
$39/month
Great choice for companies winth more than 10 employees
- 5000 emails limit
- 100GB inbox
- Next super feature
- Custom integrations
Pro
Pro product
$79/month
No limits! Best option for huge companies
- Unlimited inboxes
- Unlimited emails
- Unlimited space
Basic
Basic product
$19/month
Best offer for small companies
- Free Editor
- 1000 emails limit
- 10GB inbox

Standard
Basic product
$39/month
Great choice for companies winth more than 10 employees
- 5000 emails limit
- 100GB inbox
- Next super feature
- Custom integrations
Best choice
Pro
Basic product
$79/month
No limits! Best option for huge companies
- Unlimited inboxes
- Unlimited emails
- Unlimited space
block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wlc/pricing",
"title": " Pricing",
"category": "theme",
"icon": "tag",
"description": "Pricing",
"keywords": [ "pricing" ],
"attributes":{
"isFeatured": {
"type": "boolean",
"default": false
},
"featuredLabel": {
"type": "string",
"default": "Featured"
},
"title": {
"type": "string",
"default": "Pricing"
},
"description": {
"type": "string",
"default": "New product"
},
"currency": {
"type": "string",
"default": "$"
},
"currencyPosition": {
"type": "string",
"default": "before"
},
"price": {
"type": "string",
"default": "0"
},
"period": {
"type": "string",
"default": "month"
},
"buttonLabel": {
"type": "string",
"default": "Button"
},
"buttonUrl": {
"type": "string",
"default": "#"
},
"buttonBackgroundColor": {
"type": "string",
"default": "#000000"
},
"buttonTextColor": {
"type": "string",
"default": "#FFFFFF"
},
"buttonTextColorOnHover": {
"type": "string",
"default": "#000000"
},
"buttonBackgroundColorOnHover": {
"type": "string",
"default": "#FFFFFF"
},
"priceDescription": {
"type": "string",
"default": "Text below price"
},
"features": {
"type": "array",
"default": [
"Feature 1",
"Feature 2",
"Feature 3"
]
},
"isDarkMode": {
"type": "boolean",
"default": false
},
"darkModeBackgroundColor": {
"type": "string",
"default": "#000000"
},
"darkModeTextColor": {
"type": "string",
"default": "#FFFFFF"
},
"buttonPosition": {
"type": "string",
"default": "bottom"
},
"imageUrl": {
"type": "string",
"default": ""
}
},
"supports": {
"anchor": true,
"align": true,
"color": {
"gradient": true
},
"spacing": {
"margin": [ "vertical" ],
"padding": true
},
"typography": {
"fontSize": true,
"lineHeight": true
},
"renaming": true
},
"version": "1.0.0",
"textdomain": "WLC",
"editorScript": "file:./index.js",
"script": "file:./view.js",
"style": "file:./style-index.css"
}edit.js
import {
InspectorControls,
useBlockProps,
RichText,
URLInput,
MediaUpload,
MediaUploadCheck,
} from "@wordpress/block-editor";
import {
TextControl,
PanelBody,
Button,
ToggleControl,
Icon,
ColorPicker,
SelectControl,
} from "@wordpress/components";
import { __ } from "@wordpress/i18n";
import { useState, useCallback, useRef } from "react";
import { close } from "@wordpress/icons";
const edit = ({ attributes, setAttributes }) => {
const renderCount = useRef(0);
renderCount.current += 1;
const {
title,
description,
currency,
currencyPosition,
price,
features,
period,
priceDescription,
buttonLabel,
buttonUrl,
buttonPosition,
buttonBackgroundColor,
buttonTextColor,
buttonTextColorOnHover,
buttonBackgroundColorOnHover,
isFeatured,
featuredLabel,
isDarkMode,
darkModeBackgroundColor,
darkModeTextColor,
imageUrl,
} = attributes;
const blockProps = useBlockProps({
className: `pricing-block ${isFeatured ? "is-featured" : ""} ${isDarkMode ? "dark-mode" : ""}`,
style: {
"--pricing-button-text-color": buttonTextColor,
"--pricing-button-text-color-on-hover": buttonTextColorOnHover,
"--pricing-button-bg-color": buttonBackgroundColor,
"--pricing-button-bg-color-on-hover": buttonBackgroundColorOnHover,
},
});
const [newFeature, setNewFeature] = useState("");
const handleAddFeature = useCallback(() => {
if (newFeature.trim() !== "") {
setAttributes({ features: [...features, newFeature] });
setNewFeature("");
}
}, [newFeature, features, setAttributes]);
const handleRemoveFeature = useCallback(
(index) => {
const updatedFeatures = features.filter((_, i) => i !== index);
setAttributes({ features: updatedFeatures });
},
[features, setAttributes],
);
const handleFeatureChange = useCallback(
(value, index) => {
const updatedFeatures = [...features];
updatedFeatures[index] = value;
setAttributes({ features: updatedFeatures });
},
[features, setAttributes],
);
const handleColorChange = (color, colorType) => {
setAttributes({ [colorType]: color.hex });
};
const MediaUploader = () => {
const onSelectImage = useCallback(
(media) => {
setAttributes({ imageUrl: media.url });
},
[setAttributes],
);
const getImageName = useCallback((url) => {
if (!url) return "";
const parts = url.split("/");
return parts[parts.length - 1];
}, []);
const onRemoveImage = useCallback(() => {
setAttributes({ imageUrl: "" });
}, [setAttributes]);
return (
<MediaUploadCheck>
<MediaUpload
onSelect={onSelectImage}
allowedTypes={["image"]}
value={imageUrl}
render={({ open }) => (
<div className="components-base-control">
{imageUrl ? (
<>
<div>{__("Uploaded Image", "WLC")}</div>
<Button onClick={open} isDefault>
{getImageName(imageUrl)}
</Button>
<Button onClick={onRemoveImage} isDestructive>
{__("Remove Image", "WLC")}
</Button>
</>
) : (
<Button onClick={open} isDefault>
{__("Upload Image", "WLC")}
</Button>
)}
</div>
)}
/>
</MediaUploadCheck>
);
};
const PricingButton = () => {
return (
buttonUrl &&
buttonLabel && (
<a href={buttonUrl} className="pricing-block__button">
{buttonLabel}
</a>
)
);
};
return (
<>
<InspectorControls>
<PanelBody title={__("Pricing Settings", "WLC")}>
<ToggleControl
label={__("Featured", "WLC")}
checked={isFeatured}
onChange={(value) => setAttributes({ isFeatured: value })}
/>
{isFeatured && (
<TextControl
label={__("Featured Label", "WLC")}
value={featuredLabel}
onChange={(value) => setAttributes({ featuredLabel: value })}
placeholder={__("Enter featured label...", "WLC")}
/>
)}
<MediaUploader />
<TextControl
label={__("Title", "WLC")}
value={title}
onChange={(value) => setAttributes({ title: value })}
/>
<TextControl
label={__("Description", "WLC")}
value={description}
onChange={(value) => setAttributes({ description: value })}
/>
<TextControl
label={__("Currency", "WLC")}
value={currency}
onChange={(value) => setAttributes({ currency: value })}
/>
<SelectControl
label={__("Currency Position", "WLC")}
value={currencyPosition}
options={[
{ label: __("Before price", "WLC"), value: "before" },
{ label: __("After price", "WLC"), value: "after" },
]}
onChange={(value) => setAttributes({ currencyPosition: value })}
/>
<TextControl
label={__("Price", "WLC")}
value={price}
onChange={(value) => setAttributes({ price: value })}
/>
<TextControl
label={__("Period", "WLC")}
value={period}
onChange={(value) => setAttributes({ period: value })}
/>
<TextControl
label={__("Price description", "WLC")}
value={priceDescription}
onChange={(value) => setAttributes({ priceDescription: value })}
/>
<SelectControl
label={__("Button Position", "WLC")}
value={buttonPosition}
options={[
{ label: __("Bottom Section", "WLC"), value: "bottom" },
{ label: __("Top Section", "WLC"), value: "top" },
]}
onChange={(value) => setAttributes({ buttonPosition: value })}
/>
<TextControl
label={__("Button Label", "WLC")}
value={buttonLabel}
onChange={(value) => setAttributes({ buttonLabel: value })}
/>
<URLInput
label={__("Button URL", "WLC")}
value={buttonUrl}
onChange={(value) => setAttributes({ buttonUrl: value })}
/>
</PanelBody>
</InspectorControls>
<InspectorControls group="styles">
<PanelBody title={__("Pricing Styles", "WLC")}>
<ToggleControl
label={__("Dark Mode", "WLC")}
checked={isDarkMode}
onChange={(value) => setAttributes({ isDarkMode: value })}
/>
{isDarkMode && (
<>
<div>
<label>{__("Dark Mode Background Color", "WLC")}</label>
<ColorPicker
color={darkModeBackgroundColor}
onChangeComplete={(color) =>
handleColorChange(color, "darkModeBackgroundColor")
}
disableAlpha
/>
</div>
<div>
<label>{__("Dark Mode Text Color", "WLC")}</label>
<ColorPicker
color={darkModeTextColor}
onChangeComplete={(color) =>
handleColorChange(color, "darkModeTextColor")
}
disableAlpha
/>
</div>
</>
)}
<div>
<label>{__("Button Text Color", "WLC")}</label>
<ColorPicker
color={buttonTextColor}
onChangeComplete={(color) =>
handleColorChange(color, "buttonTextColor")
}
disableAlpha
/>
</div>
<div>
<label>{__("Button Background Color", "WLC")}</label>
<ColorPicker
color={buttonBackgroundColor}
onChangeComplete={(color) =>
handleColorChange(color, "buttonBackgroundColor")
}
disableAlpha
/>
</div>
<div>
<label>{__("Button Text Hover Color", "WLC")}</label>
<ColorPicker
color={buttonTextColorOnHover}
onChangeComplete={(color) =>
handleColorChange(color, "buttonTextColorOnHover")
}
disableAlpha
/>
</div>
<div>
<label>{__("Button Background Hover Color", "WLC")}</label>
<ColorPicker
color={buttonBackgroundColorOnHover}
onChangeComplete={(color) =>
handleColorChange(color, "buttonBackgroundColorOnHover")
}
disableAlpha
/>
</div>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
<div
className="pricing-block__top-section"
style={{
color: isDarkMode ? darkModeTextColor : "inherit",
backgroundColor: isDarkMode ? darkModeBackgroundColor : "inherit",
}}
>
{imageUrl && <img src={imageUrl} alt="Decorative" />}
{isFeatured && (
<RichText
tagName="div"
className="pricing-block__featured-label"
value={featuredLabel}
onChange={(value) => setAttributes({ featuredLabel: value })}
/>
)}
{title && (
<RichText
tagName="p"
className="pricing-block__title"
value={title}
onChange={(value) => setAttributes({ title: value })}
/>
)}
{description && (
<RichText
tagName="p"
className="pricing-block__description"
value={description}
onChange={(value) => setAttributes({ description: value })}
/>
)}
<div className="pricing-block__currency">
{currencyPosition === "before" && currency && (
<RichText
tagName="span"
className="pricing-block__currency"
value={currency}
onChange={(value) => setAttributes({ currency: value })}
/>
)}
{price && (
<RichText
tagName="span"
className="pricing-block__price"
value={price}
onChange={(value) => setAttributes({ price: value })}
/>
)}
{currencyPosition === "after" && currency && (
<RichText
tagName="span"
className="pricing-block__currency"
value={currency}
onChange={(value) => setAttributes({ currency: value })}
/>
)}
<RichText
tagName="span"
className="pricing-block__separator"
value="/"
placeholder="/"
/>
{period && (
<RichText
tagName="span"
className="pricing-block__period"
value={period}
onChange={(value) => setAttributes({ period: value })}
/>
)}
</div>
{priceDescription && (
<RichText
tagName="p"
className="pricing-block__price-description"
value={priceDescription}
onChange={(value) => setAttributes({ priceDescription: value })}
/>
)}
{buttonPosition === "top" && <PricingButton />}
</div>
<div className="pricing-block__bottom-section">
<div className="pricing-block__features-container">
<ul className="pricing-block__features">
{features &&
features.length > 0 &&
features.map((feature, index) => (
<li
key={index}
className="pricing-block__features__feature-item"
>
<RichText
tagName="span"
className="pricing-block__feature-text"
value={feature}
onChange={(value) => handleFeatureChange(value, index)}
placeholder={__("Feature...", "WLC")}
/>
<Button
isDestructive
onClick={() => handleRemoveFeature(index)}
className="pricing-block__features__feature-item__remove-button"
>
<Icon icon={close} />
</Button>
</li>
))}
</ul>
<TextControl
value={newFeature}
onChange={(value) => setNewFeature(value)}
onKeyPress={(e) => {
if (e.key === "Enter") {
handleAddFeature();
e.preventDefault();
}
}}
placeholder={__("Enter a feature and press Enter...", "WLC")}
className="new-feature-input"
/>
<Button
isSecondary
className="add-feature-button"
onClick={handleAddFeature}
>
Add Feature
</Button>
</div>
{buttonPosition === "bottom" && <PricingButton />}
</div>
</div>
</>
);
};
export default edit;
edit.jsx
import {
InspectorControls,
useBlockProps,
RichText,
URLInput,
MediaUpload,
MediaUploadCheck,
} from "@wordpress/block-editor";
import {
TextControl,
PanelBody,
Button,
ToggleControl,
Icon,
ColorPicker,
SelectControl,
} from "@wordpress/components";
import { __ } from "@wordpress/i18n";
import { React, useState, useCallback } from "react";
import { close } from "@wordpress/icons";
const MediaUploader = ({ imageUrl, setAttributes }) => {
const onSelectImage = useCallback(
(media) => {
setAttributes({ imageUrl: media.url });
},
[setAttributes],
);
const getImageName = useCallback((url) => {
if (!url) return "";
const parts = url.split("/");
return parts[parts.length - 1];
}, []);
const onRemoveImage = useCallback(() => {
setAttributes({ imageUrl: "" });
}, [setAttributes]);
return (
<MediaUploadCheck>
<MediaUpload
onSelect={onSelectImage}
allowedTypes={["image"]}
value={imageUrl}
render={({ open }) => (
<div className="components-base-control">
{imageUrl ? (
<>
<div>{__("Uploaded Image", "WLC")}</div>
<Button onClick={open} isDefault>
{getImageName(imageUrl)}
</Button>
<Button onClick={onRemoveImage} isDestructive>
{__("Remove Image", "WLC")}
</Button>
</>
) : (
<Button onClick={open} isDefault>
{__("Upload Image", "WLC")}
</Button>
)}
</div>
)}
/>
</MediaUploadCheck>
);
};
const PricingButton = ({ buttonUrl, buttonLabel }) =>
buttonUrl &&
buttonLabel && (
<a href={buttonUrl} className="pricing-block__button">
{buttonLabel}
</a>
);
const Edit = ({ attributes, setAttributes }) => {
const {
title,
description,
currency,
currencyPosition,
price,
features,
period,
priceDescription,
buttonLabel,
buttonUrl,
buttonPosition,
buttonBackgroundColor,
buttonTextColor,
buttonTextColorOnHover,
buttonBackgroundColorOnHover,
isFeatured,
featuredLabel,
isDarkMode,
darkModeBackgroundColor,
darkModeTextColor,
imageUrl,
} = attributes;
const blockProps = useBlockProps({
className: `pricing-block ${isFeatured ? "is-featured" : ""} ${isDarkMode ? "dark-mode" : ""}`,
style: {
"--pricing-button-text-color": buttonTextColor,
"--pricing-button-text-color-on-hover": buttonTextColorOnHover,
"--pricing-button-bg-color": buttonBackgroundColor,
"--pricing-button-bg-color-on-hover": buttonBackgroundColorOnHover,
},
});
const [newFeature, setNewFeature] = useState("");
const handleAddFeature = useCallback(() => {
if (newFeature.trim() !== "") {
setAttributes({ features: [...features, newFeature] });
setNewFeature("");
}
}, [newFeature, features, setAttributes]);
const handleRemoveFeature = useCallback(
(index) => {
const updatedFeatures = features.filter((_, i) => i !== index);
setAttributes({ features: updatedFeatures });
},
[features, setAttributes],
);
const handleFeatureChange = useCallback(
(value, index) => {
const updatedFeatures = [...features];
updatedFeatures[index] = value;
setAttributes({ features: updatedFeatures });
},
[features, setAttributes],
);
const handleColorChange = (color, colorType) => {
setAttributes({ [colorType]: color.hex });
};
return (
<>
<InspectorControls>
<PanelBody title={__("Pricing Settings", "WLC")}>
<ToggleControl
label={__("Featured", "WLC")}
checked={isFeatured}
onChange={(value) => setAttributes({ isFeatured: value })}
/>
{isFeatured && (
<TextControl
label={__("Featured Label", "WLC")}
value={featuredLabel}
onChange={(value) => setAttributes({ featuredLabel: value })}
placeholder={__("Enter featured label...", "WLC")}
/>
)}
<MediaUploader imageUrl={imageUrl} setAttributes={setAttributes} />
<TextControl
label={__("Title", "WLC")}
value={title}
onChange={(value) => setAttributes({ title: value })}
/>
<TextControl
label={__("Description", "WLC")}
value={description}
onChange={(value) => setAttributes({ description: value })}
/>
<TextControl
label={__("Currency", "WLC")}
value={currency}
onChange={(value) => setAttributes({ currency: value })}
/>
<SelectControl
label={__("Currency Position", "WLC")}
value={currencyPosition}
options={[
{ label: __("Before price", "WLC"), value: "before" },
{ label: __("After price", "WLC"), value: "after" },
]}
onChange={(value) => setAttributes({ currencyPosition: value })}
/>
<TextControl
label={__("Price", "WLC")}
value={price}
onChange={(value) => setAttributes({ price: value })}
/>
<TextControl
label={__("Period", "WLC")}
value={period}
onChange={(value) => setAttributes({ period: value })}
/>
<TextControl
label={__("Price description", "WLC")}
value={priceDescription}
onChange={(value) => setAttributes({ priceDescription: value })}
/>
<SelectControl
label={__("Button Position", "WLC")}
value={buttonPosition}
options={[
{ label: __("Bottom Section", "WLC"), value: "bottom" },
{ label: __("Top Section", "WLC"), value: "top" },
]}
onChange={(value) => setAttributes({ buttonPosition: value })}
/>
<TextControl
label={__("Button Label", "WLC")}
value={buttonLabel}
onChange={(value) => setAttributes({ buttonLabel: value })}
/>
<URLInput
label={__("Button URL", "WLC")}
value={buttonUrl}
onChange={(value) => setAttributes({ buttonUrl: value })}
/>
</PanelBody>
</InspectorControls>
<InspectorControls group="styles">
<PanelBody title={__("Pricing Styles", "WLC")}>
<ToggleControl
label={__("Dark Mode", "WLC")}
checked={isDarkMode}
onChange={(value) => setAttributes({ isDarkMode: value })}
/>
{isDarkMode && (
<>
<div>
<label htmlFor="darkModeBackgroundColor">
{__("Dark Mode Background Color", "WLC")}
</label>
<ColorPicker
id="darkModeBackgroundColor"
color={darkModeBackgroundColor}
onChangeComplete={(color) =>
handleColorChange(color, "darkModeBackgroundColor")
}
disableAlpha
/>
</div>
<div>
<label htmlFor="darkModeTextColor">
{__("Dark Mode Text Color", "WLC")}
</label>
<ColorPicker
id="darkModeTextColor"
color={darkModeTextColor}
onChangeComplete={(color) =>
handleColorChange(color, "darkModeTextColor")
}
disableAlpha
/>
</div>
</>
)}
<div>
<label htmlFor="buttonTextColor">
{__("Button Text Color", "WLC")}
</label>
<ColorPicker
id="buttonTextColor"
color={buttonTextColor}
onChangeComplete={(color) =>
handleColorChange(color, "buttonTextColor")
}
disableAlpha
/>
</div>
<div>
<label htmlFor="buttonBackgroundColor">
{__("Button Background Color", "WLC")}
</label>
<ColorPicker
id="buttonBackgroundColor"
color={buttonBackgroundColor}
onChangeComplete={(color) =>
handleColorChange(color, "buttonBackgroundColor")
}
disableAlpha
/>
</div>
<div>
<label htmlFor="buttonTextColorOnHover">
{__("Button Text Hover Color", "WLC")}
</label>
<ColorPicker
id="buttonTextColorOnHover"
color={buttonTextColorOnHover}
onChangeComplete={(color) =>
handleColorChange(color, "buttonTextColorOnHover")
}
disableAlpha
/>
</div>
<div>
<label htmlFor="buttonBackgroundColorOnHover">
{__("Button Background Hover Color", "WLC")}
</label>
<ColorPicker
id="buttonBackgroundColorOnHover"
color={buttonBackgroundColorOnHover}
onChangeComplete={(color) =>
handleColorChange(color, "buttonBackgroundColorOnHover")
}
disableAlpha
/>
</div>
</PanelBody>
</InspectorControls>
<div {...blockProps}>
<div
className="pricing-block__top-section"
style={{
color: isDarkMode ? darkModeTextColor : "inherit",
backgroundColor: isDarkMode ? darkModeBackgroundColor : "inherit",
}}
>
{imageUrl && <img src={imageUrl} alt="Decorative" />}
{isFeatured && (
<RichText
tagName="div"
className="pricing-block__featured-label"
value={featuredLabel}
onChange={(value) => setAttributes({ featuredLabel: value })}
/>
)}
{title && (
<RichText
tagName="p"
className="pricing-block__title"
value={title}
onChange={(value) => setAttributes({ title: value })}
/>
)}
{description && (
<RichText
tagName="p"
className="pricing-block__description"
value={description}
onChange={(value) => setAttributes({ description: value })}
/>
)}
<div className="pricing-block__currency">
{currencyPosition === "before" && currency && (
<RichText
tagName="span"
className="pricing-block__currency"
value={currency}
onChange={(value) => setAttributes({ currency: value })}
/>
)}
{price && (
<RichText
tagName="span"
className="pricing-block__price"
value={price}
onChange={(value) => setAttributes({ price: value })}
/>
)}
{currencyPosition === "after" && currency && (
<RichText
tagName="span"
className="pricing-block__currency"
value={currency}
onChange={(value) => setAttributes({ currency: value })}
/>
)}
<RichText
tagName="span"
className="pricing-block__separator"
value="/"
placeholder="/"
/>
{period && (
<RichText
tagName="span"
className="pricing-block__period"
value={period}
onChange={(value) => setAttributes({ period: value })}
/>
)}
</div>
{priceDescription && (
<RichText
tagName="p"
className="pricing-block__price-description"
value={priceDescription}
onChange={(value) => setAttributes({ priceDescription: value })}
/>
)}
{buttonPosition === "top" && (
<PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
)}
</div>
<div className="pricing-block__bottom-section">
<div className="pricing-block__features-container">
<ul className="pricing-block__features">
{features &&
features.length > 0 &&
features.map((feature, index) => (
<li
key={`${feature}-${index}`}
className="pricing-block__features__feature-item"
>
<RichText
tagName="span"
className="pricing-block__feature-text"
value={feature}
onChange={(value) => handleFeatureChange(value, index)}
placeholder={__("Feature...", "WLC")}
/>
<Button
isDestructive
onClick={() => handleRemoveFeature(index)}
className="pricing-block__features__feature-item__remove-button"
>
<Icon icon={close} />
</Button>
</li>
))}
</ul>
<TextControl
value={newFeature}
onChange={(value) => setNewFeature(value)}
onKeyPress={(e) => {
if (e.key === "Enter") {
handleAddFeature();
e.preventDefault();
}
}}
placeholder={__("Enter a feature and press Enter...", "WLC")}
className="new-feature-input"
/>
<Button
isSecondary
className="add-feature-button"
onClick={handleAddFeature}
>
Add Feature
</Button>
</div>
{buttonPosition === "bottom" && (
<PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
)}
</div>
</div>
</>
);
};
export default Edit;
index.js
import { registerBlockType } from "@wordpress/blocks";
import metadata from './block.json';
import save from "./save";
import edit from './edit';
import './style.scss';
registerBlockType( metadata, {
edit: edit,
save: save,
});save.js
import { RichText, useBlockProps } from "@wordpress/block-editor";
import { React } from "react";
const PricingButton = ({ buttonUrl, buttonLabel }) =>
buttonUrl &&
buttonLabel && (
<a href={buttonUrl} className="pricing-block__button">
{buttonLabel}
</a>
);
const save = ({ attributes }) => {
const {
title,
description,
currency,
currencyPosition,
price,
features,
period,
priceDescription,
buttonLabel,
buttonUrl,
buttonPosition,
buttonBackgroundColor,
buttonTextColor,
buttonTextColorOnHover,
buttonBackgroundColorOnHover,
isFeatured,
featuredLabel,
isDarkMode,
darkModeBackgroundColor,
darkModeTextColor,
imageUrl,
} = attributes;
const blockProps = useBlockProps.save({
className: `pricing-block ${isFeatured ? "is-featured" : ""} ${isDarkMode ? "dark-mode" : ""}`,
style: {
"--pricing-button-text-color": buttonTextColor,
"--pricing-button-text-color-on-hover": buttonTextColorOnHover,
"--pricing-button-bg-color": buttonBackgroundColor,
"--pricing-button-bg-color-on-hover": buttonBackgroundColorOnHover,
},
});
const getStyles = () => {
return {
topSection: {
color: isDarkMode ? darkModeTextColor : "inherit",
backgroundColor: isDarkMode ? darkModeBackgroundColor : "inherit",
},
button: {
color: buttonTextColor,
backgroundColor: buttonBackgroundColor,
},
};
};
const styles = getStyles();
return (
<div {...blockProps}>
<div className="pricing-block__top-section" style={styles.topSection}>
{imageUrl && <img src={imageUrl} alt="Decorative" />}
{isFeatured && (
<RichText.Content
tagName="div"
className="pricing-block__featured-label"
value={featuredLabel}
/>
)}
<RichText.Content
tagName="p"
className="pricing-block__title"
value={title}
/>
<RichText.Content
tagName="p"
className="pricing-block__description"
value={description}
/>
<div className="pricing-block__currency">
{currencyPosition === "before" && (
<RichText.Content
tagName="span"
className="pricing-block__currency"
value={currency}
/>
)}
<RichText.Content
tagName="span"
className="pricing-block__price"
value={price}
/>
{currencyPosition === "after" && (
<RichText.Content
tagName="span"
className="pricing-block__currency"
value={currency}
/>
)}
<RichText.Content
tagName="span"
className="pricing-block__separator"
value="/"
/>
<RichText.Content
tagName="span"
className="pricing-block__period"
value={period}
/>
</div>
<RichText.Content
tagName="p"
className="pricing-block__price-description"
value={priceDescription}
/>
{buttonPosition === "top" && (
<PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
)}
</div>
<div className="pricing-block__bottom-section">
{features && features.length > 0 && (
<ul className="pricing-block__features">
{features.map((feature, index) => (
<li
key={`${feature}-${index}`}
className="pricing-block__features__feature-item"
>
{feature}
</li>
))}
</ul>
)}
{buttonPosition === "bottom" && (
<PricingButton buttonUrl={buttonUrl} buttonLabel={buttonLabel} />
)}
</div>
</div>
);
};
export default save;
style.scss
.pricing-block {
@apply relative border border-grey-300 rounded-lg bg-white shadow-md;
&.is-featured {
@apply border-blue-500 bg-blue-50;
}
&.dark-mode {
.pricing-block__top-section {
@apply p-6 pb-6;
}
.pricing-block__bottom-section {
@apply pt-6;
}
}
&__top-section {
@apply p-6 pb-2;
img {
@apply absolute top-4 right-4 w-16 h-16 object-cover;
}
}
&__bottom-section {
@apply p-6 pt-1;
&.dark-mode & {
@apply pt-6;
}
}
&__featured-label {
@apply absolute -top-4 left-1/2 transform -translate-x-1/2 bg-blue-500 text-white px-4 py-1 rounded-lg text-16 font-bold;
}
&__title {
@apply text-20 font-bold mb-2;
}
&__description {
@apply text-18 mb-4;
}
&__currency {
@apply text-60 font-bold;
}
&__price {
@apply mb-4;
}
&__separator {
@apply text-24 mb-1 font-bold;
}
&__period {
@apply text-24 mb-1 font-bold;
}
&__price-description {
@apply text-18;
}
&__features {
@apply mb-1 mt-1;
&__feature-item {
@apply flex pb-2 items-center;
&::before {
@apply content-icon-tick w-8 h-6;
}
&__feature-text {
@apply mr-2;
}
&__remove-button {
@apply p-0;
}
}
}
&__button {
@apply block text-center bg-black text-white py-2 px-4 mt-3 rounded-md transition duration-200;
@apply border border-solid;
color: var(--pricing-button-text-color);
background-color: var(--pricing-button-bg-color);
&:hover {
@apply border border-solid;
color: var(--pricing-button-text-color-on-hover);
background-color: var(--pricing-button-bg-color-on-hover);
border-color: var(--pricing-button-text-color-on-hover);
}
}
}
.add-feature-button {
@apply text-white py-2 px-4 rounded-md hover:bg-blue-300 focus:outline-none mb-4;
}
.new-feature-input {
@apply w-full mb-4 mt-1;
}
view.js
document.addEventListener( 'DOMContentLoaded', function() {
// add js to be served on frontend only
} );