From 68a33956b39999981dd8698ce6368d8c6265e281 Mon Sep 17 00:00:00 2001
From: Angela Blake
Date: Tue, 5 May 2026 11:59:21 -0500
Subject: [PATCH 01/11] Donations Block: add modal display mode with trigger
button and icon picker
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Adds `displayMode` (inline/modal), `triggerButtonText`, and `triggerIcon`
attributes. In modal mode the block renders as a styled trigger button that
opens the full donations form in an accessible overlay modal (focus trap,
ESC to close, backdrop click to close, scroll lock, return focus on close).
Includes a 3×3 curated icon picker (9 icons + None) in the editor sidebar
and a trigger button preview in the editor canvas.
Co-Authored-By: Claude Sonnet 4.6
---
.../extensions/blocks/donations/block.json | 12 ++
.../extensions/blocks/donations/controls.js | 76 ++++++++++++
.../extensions/blocks/donations/donations.php | 115 +++++++++++++++++-
.../extensions/blocks/donations/edit.js | 37 +++++-
.../extensions/blocks/donations/editor.scss | 80 ++++++++++++
.../extensions/blocks/donations/icons.js | 77 ++++++++++++
.../extensions/blocks/donations/view.js | 73 ++++++++++-
.../extensions/blocks/donations/view.scss | 78 ++++++++++++
8 files changed, 538 insertions(+), 10 deletions(-)
create mode 100644 projects/plugins/jetpack/extensions/blocks/donations/icons.js
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/block.json b/projects/plugins/jetpack/extensions/blocks/donations/block.json
index 2ac0c242e4d..c1041bcf4fd 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/block.json
+++ b/projects/plugins/jetpack/extensions/blocks/donations/block.json
@@ -180,6 +180,18 @@
},
"maximumAmount": {
"type": "number"
+ },
+ "displayMode": {
+ "type": "string",
+ "enum": [ "inline", "modal" ],
+ "default": "inline"
+ },
+ "triggerButtonText": {
+ "type": "string"
+ },
+ "triggerIcon": {
+ "type": "string",
+ "default": "coffee"
}
},
"example": {}
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/controls.js b/projects/plugins/jetpack/extensions/blocks/donations/controls.js
index 37215cfdd52..c1c707885c6 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/controls.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/controls.js
@@ -2,21 +2,26 @@ import formatCurrency, { CURRENCIES } from '@automattic/format-currency';
import { getSiteFragment } from '@automattic/jetpack-shared-extension-utils';
import { AlignmentControl, BlockControls, InspectorControls } from '@wordpress/block-editor';
import {
+ Button,
Dashicon,
Dropdown,
ExternalLink,
Flex,
FlexBlock,
FlexItem,
+ Icon,
MenuGroup,
MenuItem,
PanelBody,
SelectControl,
TextControl,
ToggleControl,
+ __experimentalToggleGroupControl as ToggleGroupControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis
+ __experimentalToggleGroupControlOption as ToggleGroupControlOption, // eslint-disable-line @wordpress/no-unsafe-wp-apis
ToolbarGroup,
ToolbarItem,
ToolbarButton,
+ Tooltip,
} from '@wordpress/components';
import { useCallback } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
@@ -26,6 +31,7 @@ import {
minimumTransactionAmountForCurrency,
SUPPORTED_CURRENCIES,
} from '../../shared/currencies';
+import { TRIGGER_ICONS } from './icons';
import { firstShownInterval } from './utils';
const INTERVAL_TO_ATTRIBUTE = {
@@ -47,6 +53,9 @@ const Controls = props => {
customAmountPlaceholder,
minimumAmount,
maximumAmount,
+ displayMode,
+ triggerButtonText,
+ triggerIcon,
} = attributes;
const stripeMin = minimumTransactionAmountForCurrency( currency );
@@ -216,6 +225,73 @@ const Controls = props => {
+
+ setAttributes( { displayMode: value } ) }
+ isBlock
+ __nextHasNoMarginBottom={ true }
+ >
+
+
+
+ { displayMode === 'modal' && (
+ <>
+ setAttributes( { triggerButtonText: value || undefined } ) }
+ style={ { marginTop: 16 } }
+ __nextHasNoMarginBottom={ true }
+ />
+
+ { __( 'Button icon', 'jetpack' ) }
+
+
+
+
+
+ { TRIGGER_ICONS.map( ( { key, label, icon } ) => (
+
+
+
+ ) ) }
+
+ >
+ ) }
+
' . $choose_amount_html . '
' : '';
+ $display_mode = $attr['displayMode'] ?? 'inline';
+
+ if ( 'modal' === $display_mode ) {
+ $trigger_text = $attr['triggerButtonText'] ?? $default_texts['triggerButtonText'];
+ $trigger_icon_key = $attr['triggerIcon'] ?? 'coffee';
+ $trigger_svg = get_trigger_icon_svg( $trigger_icon_key );
+ $modal_id = $instance_id . '-modal';
+
+ return sprintf(
+ '
+%9$s
@@ -569,24 +633,63 @@ function sanitize_css_value( $value ) {
*/
function get_default_texts() {
return array(
- 'chooseAmountText' => __( 'Choose an amount', 'jetpack' ),
- 'customAmountText' => __( 'Or enter a custom amount', 'jetpack' ),
- 'extraText' => __( 'Your contribution is appreciated.', 'jetpack' ),
- 'oneTimeDonation' => array(
+ 'chooseAmountText' => __( 'Choose an amount', 'jetpack' ),
+ 'customAmountText' => __( 'Or enter a custom amount', 'jetpack' ),
+ 'extraText' => __( 'Your contribution is appreciated.', 'jetpack' ),
+ 'triggerButtonText' => __( 'Support me', 'jetpack' ),
+ 'oneTimeDonation' => array(
'heading' => __( 'Make a one-time donation', 'jetpack' ),
'buttonText' => __( 'Donate', 'jetpack' ),
),
- 'monthlyDonation' => array(
+ 'monthlyDonation' => array(
'heading' => __( 'Make a monthly donation', 'jetpack' ),
'buttonText' => __( 'Donate monthly', 'jetpack' ),
),
- 'annualDonation' => array(
+ 'annualDonation' => array(
'heading' => __( 'Make a yearly donation', 'jetpack' ),
'buttonText' => __( 'Donate yearly', 'jetpack' ),
),
);
}
+/**
+ * Return inline SVG markup for a trigger button icon.
+ *
+ * Path data sourced from icons.js ICON_SVG_PATHS — keep in sync.
+ *
+ * @param string $icon_key Icon key (e.g. 'coffee', 'heart', 'gift').
+ * @return string SVG HTML string, or '' when key is 'none' or unknown.
+ */
+function get_trigger_icon_svg( $icon_key ) {
+ $paths = array(
+ 'heart' => array( 'd' => 'M16.5 4.5c2.206 0 4 1.794 4 4 0 4.67-5.543 8.94-8.5 11.023C9.043 17.44 3.5 13.17 3.5 8.5c0-2.206 1.794-4 4-4 1.298 0 2.522.638 3.273 1.706L12 7.953l1.227-1.746c.75-1.07 1.975-1.707 3.273-1.707m0-1.5c-1.862 0-3.505.928-4.5 2.344C11.005 3.928 9.362 3 7.5 3 4.462 3 2 5.462 2 8.5c0 5.72 6.5 10.438 10 12.85 3.5-2.412 10-7.13 10-12.85C22 5.462 19.538 3 16.5 3z' ),
+ 'gift' => array( 'd' => 'M15.333 4C16.6677 4 17.75 5.0823 17.75 6.41699V6.75C17.75 7.20058 17.6394 7.62468 17.4473 8H18.5C19.2767 8 19.9154 8.59028 19.9922 9.34668L20 9.5V18.5C20 19.3284 19.3284 20 18.5 20H5.5C4.72334 20 4.08461 19.4097 4.00781 18.6533L4 18.5V9.5L4.00781 9.34668C4.07949 8.64069 4.64069 8.07949 5.34668 8.00781L5.5 8H6.55273C6.36065 7.62468 6.25 7.20058 6.25 6.75V6.41699C6.25 5.0823 7.3323 4 8.66699 4C10.0436 4.00011 11.2604 4.68183 12 5.72559C12.7396 4.68183 13.9564 4.00011 15.333 4ZM5.5 18.5H11.25V9.5H5.5V18.5ZM12.75 18.5H18.5V9.5H12.75V18.5ZM8.66699 5.5C8.16073 5.5 7.75 5.91073 7.75 6.41699V6.75C7.75 7.44036 8.30964 8 9 8H11.2461C11.2021 6.61198 10.0657 5.50017 8.66699 5.5ZM15.333 5.5C13.9343 5.50017 12.7979 6.61198 12.7539 8H15C15.6904 8 16.25 7.44036 16.25 6.75V6.41699C16.25 5.91073 15.8393 5.5 15.333 5.5Z' ),
+ 'star' => array( 'd' => 'M11.776 4.454a.25.25 0 01.448 0l2.069 4.192a.25.25 0 00.188.137l4.626.672a.25.25 0 01.139.426l-3.348 3.263a.25.25 0 00-.072.222l.79 4.607a.25.25 0 01-.362.263l-4.138-2.175a.25.25 0 00-.232 0l-4.138 2.175a.25.25 0 01-.363-.263l.79-4.607a.25.25 0 00-.071-.222L4.754 9.881a.25.25 0 01.139-.426l4.626-.672a.25.25 0 00.188-.137l2.069-4.192z' ),
+ 'thumbs-up' => array( 'd' => 'm3 12 1 8h1.5l-1-8H3Zm15.8-2h-4.4l.8-3.6c.3-1.3-.7-2.4-1.9-2.4h-.2c-.6 0-1.2.3-1.6.8l-5 6.6c-.3.4-.4.8-.4 1.2v.2l.7 5.4v.2c.2.9 1 1.5 1.9 1.5h8.2c.9 0 1.7-.6 1.9-1.4l1.8-6c.4-1.3-.6-2.6-1.9-2.6Zm.5 2.1-1.8 6c0 .2-.3.4-.5.4H8.8c-.3 0-.5-.2-.5-.4l-.7-5.4v-.4l5-6.6c0-.1.2-.2.4-.2h.2c.3 0 .6.3.5.6l-.8 3.6c-.1.4 0 .9.3 1.3s.7.6 1.2.6h4.4c.3 0 .6.3.5.6Z' ),
+ 'smiley' => array( 'd' => 'M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5 14.67 11 15.5 11zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z' ),
+ 'coffee' => array( 'd' => 'M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4zM9 1v2M12 1v2M15 1v2' ),
+ 'tip-jar' => array( 'd' => 'M9 3h6c0-1.1-.9-2-2-2H11C9.9 1 9 1.9 9 3zm7 0H8c-.55 0-1 .45-1 1v1.5c0 .55.45 1 1 1h8c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm-1 3.5H9V19c0 1.1.9 2 2 2h2c1.1 0 2-.9 2-2V6.5zm-3 1.5h2v1.5l-1 1.5-1-1.5V8z' ),
+ 'hand-heart' => array( 'd' => 'M15.5 2.1c-1.1 0-2 .6-2.5 1.4-.5-.9-1.4-1.4-2.5-1.4C8.8 2.1 7.5 3.4 7.5 5c0 2.5 4.5 5.9 5.5 6.6 1-.7 5.5-4.1 5.5-6.6 0-1.6-1.3-2.9-3-2.9zM9 14H7l-2 7h14l-2-7h-2l-1 3H10l-1-3z' ),
+ 'people' => array(
+ 'd' => 'M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z',
+ 'fill-rule' => 'evenodd',
+ ),
+ );
+
+ if ( ! isset( $paths[ $icon_key ] ) || 'none' === $icon_key ) {
+ return '';
+ }
+
+ $icon = $paths[ $icon_key ];
+ $extra_attrs = isset( $icon['fill-rule'] ) ? ' fill-rule="' . esc_attr( $icon['fill-rule'] ) . '"' : '';
+
+ return sprintf(
+ '
',
+ esc_attr( $icon['d'] ),
+ $extra_attrs
+ );
+}
+
/**
* Make default texts available to the editor.
*/
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/edit.js b/projects/plugins/jetpack/extensions/blocks/donations/edit.js
index 49af60a41f6..b1435f1e78b 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/edit.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/edit.js
@@ -1,5 +1,5 @@
import { useBlockProps } from '@wordpress/block-editor';
-import { Spinner } from '@wordpress/components';
+import { Icon, Spinner } from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback, useState, useEffect } from '@wordpress/element';
@@ -15,6 +15,7 @@ import buildCustomStyles from './build-custom-styles';
import fetchDefaultProducts from './fetch-default-products';
import fetchStatus from './fetch-status';
import FirstTimeModal from './first-time-modal';
+import { TRIGGER_ICONS } from './icons';
import './first-time-modal.scss';
import LoadingError from './loading-error';
import StyleControls from './style-controls';
@@ -22,7 +23,8 @@ import Tabs from './tabs';
const Edit = props => {
const { attributes, setAttributes } = props;
- const { currency, tabsAppearance, className } = attributes;
+ const { currency, tabsAppearance, className, displayMode, triggerButtonText, triggerIcon } =
+ attributes;
// Migrate legacy blocks that used the block-style variation
// (`is-style-buttons` saved into `className`) over to the new
@@ -207,7 +209,36 @@ const Edit = props => {
// Memberships settings are still loading
content =
;
} else {
- content =
;
+ const triggerIconEntry = TRIGGER_ICONS.find( ( { key } ) => key === triggerIcon );
+ const triggerLabel = triggerButtonText || __( 'Support me', 'jetpack' );
+ content = (
+ <>
+ { displayMode === 'modal' && (
+
+
+
+ { __( 'Modal preview — form appears inside a modal when clicked', 'jetpack' ) }
+
+
+ ) }
+
+
+
+ >
+ );
}
// When the first time modal is closed, update the user meta to mark the donation warning as dismissed
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/editor.scss b/projects/plugins/jetpack/extensions/blocks/donations/editor.scss
index f498191b0b7..eca5c051259 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/editor.scss
+++ b/projects/plugins/jetpack/extensions/blocks/donations/editor.scss
@@ -44,6 +44,51 @@
.jetpack-block-nudge {
max-width: none;
}
+
+ // Trigger button preview (modal display mode).
+ .donations__trigger-preview {
+ margin-block-end: 16px;
+ }
+
+ .donations__trigger-button {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ cursor: default;
+ pointer-events: none;
+ }
+
+ .donations__trigger-icon {
+ flex-shrink: 0;
+ fill: currentColor;
+ }
+
+ .donations__trigger-hint {
+ font-size: 12px;
+ color: #757575;
+ margin: 6px 0 0;
+ font-style: italic;
+ }
+
+ .donations__modal-form-preview {
+ border: 1px dashed #b0b0b0;
+ border-radius: 4px;
+ padding: 12px;
+ position: relative;
+
+ &::before {
+ content: "Modal contents";
+ position: absolute;
+ inset-block-start: -10px;
+ inset-inline-start: 12px;
+ background: #fff;
+ padding: 0 4px;
+ font-size: 11px;
+ color: #757575;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+ }
}
.jetpack-donations__currency-toggle {
@@ -73,6 +118,41 @@
border-bottom: 0;
}
+// Icon picker grid in the Display panel.
+.jetpack-donations__icon-picker {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 4px;
+ margin-block-start: 4px;
+
+ .jetpack-donations__icon-option {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ min-height: 36px;
+ padding: 4px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ background: transparent;
+ cursor: pointer;
+
+ &:hover {
+ border-color: #1e1e1e;
+ }
+
+ &.is-selected {
+ border-color: var(--wp-admin-theme-color, #007cba);
+ box-shadow: 0 0 0 1px var(--wp-admin-theme-color, #007cba);
+ background: rgba(0, 124, 186, 0.08);
+ }
+
+ svg {
+ display: block;
+ }
+ }
+}
+
// Make labels inside our custom style panels inherit text color from the
// inspector sidebar (`.interface-complementary-area`) instead of the
// component default grey, matching the auto-rendered supports panels.
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/icons.js b/projects/plugins/jetpack/extensions/blocks/donations/icons.js
new file mode 100644
index 00000000000..d13a3a3b081
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/blocks/donations/icons.js
@@ -0,0 +1,77 @@
+import { __ } from '@wordpress/i18n';
+import { gift, people, starFilled, thumbsUp } from '@wordpress/icons';
+import { SVG, Path } from '@wordpress/primitives';
+
+// Heart — reuses the Donations block's own block.json icon path.
+const heart = (
+
+);
+
+// Smiley face.
+const smiley = (
+
+);
+
+// Coffee cup (hot beverage mug with handle and steam).
+const coffee = (
+
+);
+
+// Tip jar — wide-mouth jar with a coin slot in the lid.
+const tipJar = (
+
+);
+
+// Hand with heart — open hand offering a heart.
+const handWithHeart = (
+
+);
+
+// SVG path strings used by PHP (donations.php) for server-side render.
+// Keep in sync with the React icon definitions above and @wordpress/icons source.
+export const ICON_SVG_PATHS = {
+ heart:
+ 'M16.5 4.5c2.206 0 4 1.794 4 4 0 4.67-5.543 8.94-8.5 11.023C9.043 17.44 3.5 13.17 3.5 8.5c0-2.206 1.794-4 4-4 1.298 0 2.522.638 3.273 1.706L12 7.953l1.227-1.746c.75-1.07 1.975-1.707 3.273-1.707m0-1.5c-1.862 0-3.505.928-4.5 2.344C11.005 3.928 9.362 3 7.5 3 4.462 3 2 5.462 2 8.5c0 5.72 6.5 10.438 10 12.85 3.5-2.412 10-7.13 10-12.85C22 5.462 19.538 3 16.5 3z',
+ gift: 'M15.333 4C16.6677 4 17.75 5.0823 17.75 6.41699V6.75C17.75 7.20058 17.6394 7.62468 17.4473 8H18.5C19.2767 8 19.9154 8.59028 19.9922 9.34668L20 9.5V18.5C20 19.3284 19.3284 20 18.5 20H5.5C4.72334 20 4.08461 19.4097 4.00781 18.6533L4 18.5V9.5L4.00781 9.34668C4.07949 8.64069 4.64069 8.07949 5.34668 8.00781L5.5 8H6.55273C6.36065 7.62468 6.25 7.20058 6.25 6.75V6.41699C6.25 5.0823 7.3323 4 8.66699 4C10.0436 4.00011 11.2604 4.68183 12 5.72559C12.7396 4.68183 13.9564 4.00011 15.333 4ZM5.5 18.5H11.25V9.5H5.5V18.5ZM12.75 18.5H18.5V9.5H12.75V18.5ZM8.66699 5.5C8.16073 5.5 7.75 5.91073 7.75 6.41699V6.75C7.75 7.44036 8.30964 8 9 8H11.2461C11.2021 6.61198 10.0657 5.50017 8.66699 5.5ZM15.333 5.5C13.9343 5.50017 12.7979 6.61198 12.7539 8H15C15.6904 8 16.25 7.44036 16.25 6.75V6.41699C16.25 5.91073 15.8393 5.5 15.333 5.5Z',
+ star: 'M11.776 4.454a.25.25 0 01.448 0l2.069 4.192a.25.25 0 00.188.137l4.626.672a.25.25 0 01.139.426l-3.348 3.263a.25.25 0 00-.072.222l.79 4.607a.25.25 0 01-.362.263l-4.138-2.175a.25.25 0 00-.232 0l-4.138 2.175a.25.25 0 01-.363-.263l.79-4.607a.25.25 0 00-.071-.222L4.754 9.881a.25.25 0 01.139-.426l4.626-.672a.25.25 0 00.188-.137l2.069-4.192z',
+ 'thumbs-up':
+ 'm3 12 1 8h1.5l-1-8H3Zm15.8-2h-4.4l.8-3.6c.3-1.3-.7-2.4-1.9-2.4h-.2c-.6 0-1.2.3-1.6.8l-5 6.6c-.3.4-.4.8-.4 1.2v.2l.7 5.4v.2c.2.9 1 1.5 1.9 1.5h8.2c.9 0 1.7-.6 1.9-1.4l1.8-6c.4-1.3-.6-2.6-1.9-2.6Zm.5 2.1-1.8 6c0 .2-.3.4-.5.4H8.8c-.3 0-.5-.2-.5-.4l-.7-5.4v-.4l5-6.6c0-.1.2-.2.4-.2h.2c.3 0 .6.3.5.6l-.8 3.6c-.1.4 0 .9.3 1.3s.7.6 1.2.6h4.4c.3 0 .6.3.5.6Z',
+ smiley:
+ 'M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5 14.67 11 15.5 11zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z',
+ coffee:
+ 'M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4zM9 1v2M12 1v2M15 1v2',
+ 'tip-jar':
+ 'M9 3h6c0-1.1-.9-2-2-2H11C9.9 1 9 1.9 9 3zm7 0H8c-.55 0-1 .45-1 1v1.5c0 .55.45 1 1 1h8c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm-1 3.5H9V19c0 1.1.9 2 2 2h2c1.1 0 2-.9 2-2V6.5zm-3 1.5h2v1.5l-1 1.5-1-1.5V8z',
+ 'hand-heart':
+ 'M15.5 2.1c-1.1 0-2 .6-2.5 1.4-.5-.9-1.4-1.4-2.5-1.4C8.8 2.1 7.5 3.4 7.5 5c0 2.5 4.5 5.9 5.5 6.6 1-.7 5.5-4.1 5.5-6.6 0-1.6-1.3-2.9-3-2.9zM9 14H7l-2 7h14l-2-7h-2l-1 3H10l-1-3z',
+ people:
+ 'M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z',
+};
+
+// Ordered list used to build the icon picker grid (3×3).
+// Keys match ICON_SVG_PATHS and the triggerIcon block attribute.
+export const TRIGGER_ICONS = [
+ { key: 'heart', label: __( 'Heart', 'jetpack' ), icon: heart },
+ { key: 'gift', label: __( 'Gift', 'jetpack' ), icon: gift },
+ { key: 'star', label: __( 'Star', 'jetpack' ), icon: starFilled },
+ { key: 'thumbs-up', label: __( 'Thumbs up', 'jetpack' ), icon: thumbsUp },
+ { key: 'smiley', label: __( 'Smiley face', 'jetpack' ), icon: smiley },
+ { key: 'coffee', label: __( 'Coffee cup', 'jetpack' ), icon: coffee },
+ { key: 'tip-jar', label: __( 'Tip jar', 'jetpack' ), icon: tipJar },
+ { key: 'hand-heart', label: __( 'Hand with heart', 'jetpack' ), icon: handWithHeart },
+ { key: 'people', label: __( 'People', 'jetpack' ), icon: people },
+];
+
+// @wordpress/icons exports needed by PHP path map (gift, starFilled, thumbsUp, people
+// are React elements — PHP uses its own SVG strings for those).
+export { gift, people, starFilled, thumbsUp };
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/view.js b/projects/plugins/jetpack/extensions/blocks/donations/view.js
index 7a5d7ca205d..44e9fe2d311 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/view.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/view.js
@@ -323,8 +323,79 @@ class JetpackDonations {
}
}
+class JetpackDonationsModal {
+ constructor( block ) {
+ this.block = block;
+ this.trigger = block.querySelector( '.donations__trigger-button' );
+ this.overlay = block.querySelector( '.donations__modal-overlay' );
+ this.closeBtn = block.querySelector( '.donations__modal-close' );
+
+ if ( ! this.trigger || ! this.overlay ) {
+ return;
+ }
+
+ this.trigger.addEventListener( 'click', () => this.open() );
+ this.closeBtn?.addEventListener( 'click', () => this.close() );
+ this.overlay.addEventListener( 'click', event => {
+ if ( event.target === this.overlay ) {
+ this.close();
+ }
+ } );
+ document.addEventListener( 'keydown', event => {
+ if ( event.key === 'Escape' && ! this.overlay.hidden ) {
+ this.close();
+ }
+ } );
+ }
+
+ open() {
+ this.overlay.hidden = false;
+ this.overlay.ownerDocument.body.classList.add( 'donations-modal-open' );
+ this._previousFocus = this.overlay.ownerDocument.activeElement;
+ const firstFocusable = this.overlay.querySelector(
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
+ );
+ firstFocusable?.focus();
+ this.overlay.addEventListener( 'keydown', this._trapFocus );
+ }
+
+ close() {
+ this.overlay.hidden = true;
+ this.overlay.ownerDocument.body.classList.remove( 'donations-modal-open' );
+ this.overlay.removeEventListener( 'keydown', this._trapFocus );
+ this._previousFocus?.focus();
+ }
+
+ _trapFocus = event => {
+ if ( event.key !== 'Tab' ) {
+ return;
+ }
+ const focusable = Array.from(
+ this.overlay.querySelectorAll(
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
+ )
+ ).filter( el => ! el.closest( '[hidden]' ) );
+ if ( ! focusable.length ) {
+ return;
+ }
+ const first = focusable[ 0 ];
+ const last = focusable[ focusable.length - 1 ];
+ if ( event.shiftKey && this.overlay.ownerDocument.activeElement === first ) {
+ event.preventDefault();
+ last.focus();
+ } else if ( ! event.shiftKey && this.overlay.ownerDocument.activeElement === last ) {
+ event.preventDefault();
+ first.focus();
+ }
+ };
+}
+
domReady( () => {
const blocks = document.querySelectorAll( '.wp-block-jetpack-donations' );
- blocks.forEach( block => new JetpackDonations( block ) );
+ blocks.forEach( block =>
+ block.querySelector( '.donations__modal-overlay' )
+ ? [ new JetpackDonationsModal( block ), new JetpackDonations( block ) ]
+ : new JetpackDonations( block )
+ );
initializeMembershipButtons( '.donations__donate-button' );
} );
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/view.scss b/projects/plugins/jetpack/extensions/blocks/donations/view.scss
index e537c1d3b7e..8bf05e14496 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/view.scss
+++ b/projects/plugins/jetpack/extensions/blocks/donations/view.scss
@@ -122,4 +122,82 @@
pointer-events: none;
opacity: 0.2;
}
+
+ // Trigger button: icon + text inline.
+ .donations__trigger-button {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .donations__trigger-icon {
+ flex-shrink: 0;
+ fill: currentColor;
+ }
+
+ // Modal overlay: covers viewport, blurred backdrop.
+ .donations__modal-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 100000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.45);
+ backdrop-filter: blur(5px);
+
+ &[hidden] {
+ display: none;
+ }
+ }
+
+ // Modal dialog container.
+ .donations__modal-dialog {
+ position: relative;
+ width: 90%;
+ max-width: 640px;
+ max-height: 90vh;
+ overflow-y: auto;
+ background: var(--wp--style--color--background, #fff);
+ border-radius: inherit;
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.18);
+
+ @media (max-width: 600px) {
+ width: 92%;
+ }
+ }
+
+ .donations__modal-content {
+ padding: 40px;
+
+ @media (max-width: 600px) {
+ padding: 24px 20px;
+ }
+ }
+
+ // Close button: X in upper-right corner.
+ .donations__modal-close {
+ position: absolute;
+ inset-block-start: 12px;
+ inset-inline-end: 12px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ padding: 6px;
+ line-height: 1;
+ color: inherit;
+ font-size: 18px;
+ opacity: 0.6;
+ border-radius: 4px;
+
+ &:hover,
+ &:focus-visible {
+ opacity: 1;
+ }
+ }
+}
+
+// Prevent body scroll when modal is open.
+body.donations-modal-open {
+ overflow: hidden;
}
From f351e696a522af732f2b6ed77240aab910714767 Mon Sep 17 00:00:00 2001
From: Angela Blake
Date: Tue, 5 May 2026 17:22:22 -0500
Subject: [PATCH 02/11] Donations Block: modal display mode polish from testing
- Fix block border: reset border/radius on wrapper (!important to override
inline block-support styles) and forward them to the modal dialog instead;
overflow:hidden on dialog enables border-radius clipping
- Add trigger button alignment: contentAlignment now also applies text-align
to the block wrapper in modal mode, aligning the trigger button left/center/right
- Simplify icon picker to 4 icons (heart, gift, smiley, cup) in a single row;
replace the "None" grid button with a Show icon ToggleControl consistent
with other settings panels
Co-Authored-By: Claude Sonnet 4.6
---
.../extensions/blocks/donations/controls.js | 69 +++++++------------
.../extensions/blocks/donations/donations.php | 52 +++++++++++++-
.../extensions/blocks/donations/icons.js | 31 ++-------
.../extensions/blocks/donations/view.scss | 9 ++-
4 files changed, 87 insertions(+), 74 deletions(-)
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/controls.js b/projects/plugins/jetpack/extensions/blocks/donations/controls.js
index c1c707885c6..3a43763f57b 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/controls.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/controls.js
@@ -246,49 +246,32 @@ const Controls = props => {
style={ { marginTop: 16 } }
__nextHasNoMarginBottom={ true }
/>
-
- { __( 'Button icon', 'jetpack' ) }
-
-
-
-
-
- { TRIGGER_ICONS.map( ( { key, label, icon } ) => (
-
-
-
- ) ) }
-
+ setAttributes( { triggerIcon: value ? 'coffee' : 'none' } ) }
+ style={ { marginTop: 16 } }
+ __nextHasNoMarginBottom={ true }
+ />
+ { triggerIcon !== 'none' && (
+
+ { TRIGGER_ICONS.map( ( { key, label, icon } ) => (
+
+
+
+ ) ) }
+
+ ) }
>
) }
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/donations.php b/projects/plugins/jetpack/extensions/blocks/donations/donations.php
index 70b074cfdf5..d4a5f3aa345 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/donations.php
+++ b/projects/plugins/jetpack/extensions/blocks/donations/donations.php
@@ -245,11 +245,15 @@ function render_block( $attr, $content ) {
);
}
+ $display_mode = $attr['displayMode'] ?? 'inline';
$instance_id = wp_unique_id( 'jp-donations-' );
$instance_classes = $instance_id;
if ( isset( $attr['tabsAppearance'] ) && 'buttons' === $attr['tabsAppearance'] ) {
$instance_classes .= ' is-style-buttons';
}
+ if ( 'modal' === $display_mode ) {
+ $instance_classes .= ' is-modal-display';
+ }
$wrapper_attr_array = array( 'class' => $instance_classes );
if ( $default_interval ) {
$wrapper_attr_array['data-default-interval'] = $default_interval;
@@ -261,8 +265,6 @@ function render_block( $attr, $content ) {
$choose_amount_html = wp_kses_post( $choose_amount_text );
$choose_amount_block = '' !== trim( $choose_amount_html ) ? '' . $choose_amount_html . '
' : '';
- $display_mode = $attr['displayMode'] ?? 'inline';
-
if ( 'modal' === $display_mode ) {
$trigger_text = $attr['triggerButtonText'] ?? $default_texts['triggerButtonText'];
$trigger_icon_key = $attr['triggerIcon'] ?? 'coffee';
@@ -524,6 +526,52 @@ function build_custom_styles( $attr, $scope ) {
. $scope . ' .donations__donate-button{display:block;width:100%;box-sizing:border-box;text-align:center}';
}
+ // Modal display mode: the block-support border/radius are applied as inline
+ // styles on the wrapper div (which shows the trigger button in modal mode).
+ // Reset them on the wrapper with !important and forward them to the modal
+ // dialog instead. Also wire up trigger-button alignment via text-align.
+ if ( 'modal' === ( $attr['displayMode'] ?? 'inline' ) ) {
+ $rules[] = $scope . '{border:none!important;border-radius:0!important;overflow:visible}';
+
+ $supports_border = isset( $attr['style']['border'] ) && is_array( $attr['style']['border'] )
+ ? $attr['style']['border']
+ : array();
+ $modal_decls = array();
+
+ // Uniform border color / style / width.
+ foreach ( array( 'color', 'style', 'width' ) as $prop ) {
+ $val = sanitize_css_value( $supports_border[ $prop ] ?? '' );
+ if ( '' !== $val ) {
+ $modal_decls[] = 'border-' . $prop . ':' . $val;
+ }
+ }
+ // Per-side border (split BorderBoxControl output).
+ foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) {
+ if ( ! isset( $supports_border[ $side ] ) || ! is_array( $supports_border[ $side ] ) ) {
+ continue;
+ }
+ foreach ( array( 'color', 'style', 'width' ) as $prop ) {
+ $val = sanitize_css_value( $supports_border[ $side ][ $prop ] ?? '' );
+ if ( '' !== $val ) {
+ $modal_decls[] = 'border-' . $side . '-' . $prop . ':' . $val;
+ }
+ }
+ }
+ // Border radius.
+ $radius_val = $supports_border['radius'] ?? null;
+ $modal_decls = array_merge( $modal_decls, build_radius_decls( $radius_val ) );
+
+ if ( $modal_decls ) {
+ // overflow:hidden clips content to the border-radius.
+ $rules[] = $scope . ' .donations__modal-dialog{' . implode( ';', $modal_decls ) . ';overflow:hidden}';
+ }
+
+ // Trigger button alignment reuses the existing contentAlignment attribute.
+ if ( in_array( $content_alignment, array( 'left', 'center', 'right' ), true ) ) {
+ $rules[] = $scope . '{text-align:' . $content_alignment . '}';
+ }
+ }
+
return implode( '', $rules );
}
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/icons.js b/projects/plugins/jetpack/extensions/blocks/donations/icons.js
index d13a3a3b081..24f248dbe4c 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/icons.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/icons.js
@@ -1,5 +1,5 @@
import { __ } from '@wordpress/i18n';
-import { gift, people, starFilled, thumbsUp } from '@wordpress/icons';
+import { gift } from '@wordpress/icons';
import { SVG, Path } from '@wordpress/primitives';
// Heart — reuses the Donations block's own block.json icon path.
@@ -23,20 +23,6 @@ const coffee = (
);
-// Tip jar — wide-mouth jar with a coin slot in the lid.
-const tipJar = (
-
-);
-
-// Hand with heart — open hand offering a heart.
-const handWithHeart = (
-
-);
-
// SVG path strings used by PHP (donations.php) for server-side render.
// Keep in sync with the React icon definitions above and @wordpress/icons source.
export const ICON_SVG_PATHS = {
@@ -58,20 +44,13 @@ export const ICON_SVG_PATHS = {
'M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z',
};
-// Ordered list used to build the icon picker grid (3×3).
+// Ordered list used to build the icon picker row (4 icons).
// Keys match ICON_SVG_PATHS and the triggerIcon block attribute.
export const TRIGGER_ICONS = [
{ key: 'heart', label: __( 'Heart', 'jetpack' ), icon: heart },
{ key: 'gift', label: __( 'Gift', 'jetpack' ), icon: gift },
- { key: 'star', label: __( 'Star', 'jetpack' ), icon: starFilled },
- { key: 'thumbs-up', label: __( 'Thumbs up', 'jetpack' ), icon: thumbsUp },
- { key: 'smiley', label: __( 'Smiley face', 'jetpack' ), icon: smiley },
- { key: 'coffee', label: __( 'Coffee cup', 'jetpack' ), icon: coffee },
- { key: 'tip-jar', label: __( 'Tip jar', 'jetpack' ), icon: tipJar },
- { key: 'hand-heart', label: __( 'Hand with heart', 'jetpack' ), icon: handWithHeart },
- { key: 'people', label: __( 'People', 'jetpack' ), icon: people },
+ { key: 'smiley', label: __( 'Smiley', 'jetpack' ), icon: smiley },
+ { key: 'coffee', label: __( 'Cup', 'jetpack' ), icon: coffee },
];
-// @wordpress/icons exports needed by PHP path map (gift, starFilled, thumbsUp, people
-// are React elements — PHP uses its own SVG strings for those).
-export { gift, people, starFilled, thumbsUp };
+export { gift };
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/view.scss b/projects/plugins/jetpack/extensions/blocks/donations/view.scss
index 8bf05e14496..cb2cf2d952d 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/view.scss
+++ b/projects/plugins/jetpack/extensions/blocks/donations/view.scss
@@ -151,15 +151,15 @@
}
}
- // Modal dialog container.
+ // Modal dialog container — clips to border-radius; border/radius set via
+ // build_custom_styles when the block has border settings configured.
.donations__modal-dialog {
position: relative;
width: 90%;
max-width: 640px;
max-height: 90vh;
- overflow-y: auto;
+ overflow: hidden;
background: var(--wp--style--color--background, #fff);
- border-radius: inherit;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.18);
@media (max-width: 600px) {
@@ -168,7 +168,10 @@
}
.donations__modal-content {
+ overflow-y: auto;
+ max-height: 90vh;
padding: 40px;
+ box-sizing: border-box;
@media (max-width: 600px) {
padding: 24px 20px;
From 900181307929fda4aa3d95e34b1c0652068c52dd Mon Sep 17 00:00:00 2001
From: Angela Blake
Date: Tue, 5 May 2026 17:25:20 -0500
Subject: [PATCH 03/11] Add changelog entry for modal display mode feature
Co-Authored-By: Claude Sonnet 4.6
---
.../plugins/jetpack/changelog/add-donations-modal-display | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 projects/plugins/jetpack/changelog/add-donations-modal-display
diff --git a/projects/plugins/jetpack/changelog/add-donations-modal-display b/projects/plugins/jetpack/changelog/add-donations-modal-display
new file mode 100644
index 00000000000..2fdaa793478
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-donations-modal-display
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Donations block: add modal display mode with trigger button, configurable icon, and animated overlay.
From 08a9e4f28d1b26af73cbce1cd01ab4414ebe57f4 Mon Sep 17 00:00:00 2001
From: Angela Blake
Date: Tue, 5 May 2026 17:34:44 -0500
Subject: [PATCH 04/11] Fix Phan type errors: cast float to string for
format_price calls
Co-Authored-By: Claude Sonnet 4.6
---
.../jetpack/extensions/blocks/donations/donations.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/donations.php b/projects/plugins/jetpack/extensions/blocks/donations/donations.php
index d4a5f3aa345..ac96f87dc13 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/donations.php
+++ b/projects/plugins/jetpack/extensions/blocks/donations/donations.php
@@ -380,7 +380,7 @@ function build_security_data_attrs( $attr, $currency ) {
$attrs['data-min-error'] = sprintf(
/* translators: %s: minimum donation amount formatted with currency symbol */
__( 'The minimum donation amount is %s.', 'jetpack' ),
- \Jetpack_Currencies::format_price( $min_amount, $currency )
+ \Jetpack_Currencies::format_price( (string) $min_amount, $currency )
);
}
if ( null !== $max_amount ) {
@@ -388,14 +388,14 @@ function build_security_data_attrs( $attr, $currency ) {
$attrs['data-max-error'] = sprintf(
/* translators: %s: maximum donation amount formatted with currency symbol */
__( 'The maximum donation amount is %s.', 'jetpack' ),
- \Jetpack_Currencies::format_price( $max_amount, $currency )
+ \Jetpack_Currencies::format_price( (string) $max_amount, $currency )
);
}
$stripe_min = \Jetpack_Memberships::SUPPORTED_CURRENCIES[ $currency ] ?? 1;
$attrs['data-stripe-min-error'] = sprintf(
/* translators: %s: payment processor minimum donation amount formatted with currency symbol */
_x( 'The minimum donation amount is %s.', 'payment processor minimum', 'jetpack' ),
- \Jetpack_Currencies::format_price( $stripe_min, $currency )
+ \Jetpack_Currencies::format_price( (string) $stripe_min, $currency )
);
return $attrs;
}
From d246bd6b61f35992ead335eabf93b2b612fc127c Mon Sep 17 00:00:00 2001
From: Angela Blake
Date: Tue, 5 May 2026 18:19:31 -0500
Subject: [PATCH 05/11] Donations pop-up mode: rename labels, show only trigger
button in editor
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Rename display mode labels: Inline → In-page, Button & Modal → Pop-up
- Pop-up mode editor now shows just the trigger button (matching the
frontend) instead of the full form preview
- Remove dead editor styles for the modal form preview and hint text
Co-Authored-By: Claude Sonnet 4.6
---
.../extensions/blocks/donations/controls.js | 4 +-
.../extensions/blocks/donations/edit.js | 38 ++++++-------------
.../extensions/blocks/donations/editor.scss | 33 +---------------
3 files changed, 15 insertions(+), 60 deletions(-)
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/controls.js b/projects/plugins/jetpack/extensions/blocks/donations/controls.js
index 3a43763f57b..0f2cb0e7d63 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/controls.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/controls.js
@@ -233,8 +233,8 @@ const Controls = props => {
isBlock
__nextHasNoMarginBottom={ true }
>
-
-
+
+
{ displayMode === 'modal' && (
<>
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/edit.js b/projects/plugins/jetpack/extensions/blocks/donations/edit.js
index b1435f1e78b..31016e5aded 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/edit.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/edit.js
@@ -208,37 +208,23 @@ const Edit = props => {
} else if ( ! currency ) {
// Memberships settings are still loading
content = ;
- } else {
+ } else if ( displayMode === 'modal' ) {
const triggerIconEntry = TRIGGER_ICONS.find( ( { key } ) => key === triggerIcon );
const triggerLabel = triggerButtonText || __( 'Support me', 'jetpack' );
content = (
- <>
- { displayMode === 'modal' && (
-
-
-
- { __( 'Modal preview — form appears inside a modal when clicked', 'jetpack' ) }
-
-
+
);
+ } else {
+ content = ;
}
// When the first time modal is closed, update the user meta to mark the donation warning as dismissed
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/editor.scss b/projects/plugins/jetpack/extensions/blocks/donations/editor.scss
index eca5c051259..62f5cc206d4 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/editor.scss
+++ b/projects/plugins/jetpack/extensions/blocks/donations/editor.scss
@@ -45,11 +45,7 @@
max-width: none;
}
- // Trigger button preview (modal display mode).
- .donations__trigger-preview {
- margin-block-end: 16px;
- }
-
+ // Trigger button preview (pop-up display mode).
.donations__trigger-button {
display: inline-flex;
align-items: center;
@@ -62,33 +58,6 @@
flex-shrink: 0;
fill: currentColor;
}
-
- .donations__trigger-hint {
- font-size: 12px;
- color: #757575;
- margin: 6px 0 0;
- font-style: italic;
- }
-
- .donations__modal-form-preview {
- border: 1px dashed #b0b0b0;
- border-radius: 4px;
- padding: 12px;
- position: relative;
-
- &::before {
- content: "Modal contents";
- position: absolute;
- inset-block-start: -10px;
- inset-inline-start: 12px;
- background: #fff;
- padding: 0 4px;
- font-size: 11px;
- color: #757575;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- }
- }
}
.jetpack-donations__currency-toggle {
From 03b8a93490b67ff3d996546b540b7b4d7160660a Mon Sep 17 00:00:00 2001
From: Angela Blake
Date: Wed, 6 May 2026 10:24:11 -0500
Subject: [PATCH 06/11] Fix pop-up mode: restore inspector controls, remove
border !important hack
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Import and render Controls in edit.js for pop-up mode so all inspector
panels (Display, Settings, Security) are available regardless of mode
- Remove border:none!important and border forwarding to modal dialog;
block-support border now applies to the wrapper (trigger button) as
expected — users can style it normally
- Remove dead is-modal-display class that existed only to support the reset
Co-Authored-By: Claude Sonnet 4.6
---
.../extensions/blocks/donations/donations.php | 45 +------------------
.../extensions/blocks/donations/edit.js | 24 +++++-----
2 files changed, 16 insertions(+), 53 deletions(-)
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/donations.php b/projects/plugins/jetpack/extensions/blocks/donations/donations.php
index ac96f87dc13..94b7593276b 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/donations.php
+++ b/projects/plugins/jetpack/extensions/blocks/donations/donations.php
@@ -251,9 +251,6 @@ function render_block( $attr, $content ) {
if ( isset( $attr['tabsAppearance'] ) && 'buttons' === $attr['tabsAppearance'] ) {
$instance_classes .= ' is-style-buttons';
}
- if ( 'modal' === $display_mode ) {
- $instance_classes .= ' is-modal-display';
- }
$wrapper_attr_array = array( 'class' => $instance_classes );
if ( $default_interval ) {
$wrapper_attr_array['data-default-interval'] = $default_interval;
@@ -526,47 +523,9 @@ function build_custom_styles( $attr, $scope ) {
. $scope . ' .donations__donate-button{display:block;width:100%;box-sizing:border-box;text-align:center}';
}
- // Modal display mode: the block-support border/radius are applied as inline
- // styles on the wrapper div (which shows the trigger button in modal mode).
- // Reset them on the wrapper with !important and forward them to the modal
- // dialog instead. Also wire up trigger-button alignment via text-align.
+ // Pop-up display mode: wire up trigger-button alignment via text-align on
+ // the wrapper so the inline-flex button responds to the alignment toolbar.
if ( 'modal' === ( $attr['displayMode'] ?? 'inline' ) ) {
- $rules[] = $scope . '{border:none!important;border-radius:0!important;overflow:visible}';
-
- $supports_border = isset( $attr['style']['border'] ) && is_array( $attr['style']['border'] )
- ? $attr['style']['border']
- : array();
- $modal_decls = array();
-
- // Uniform border color / style / width.
- foreach ( array( 'color', 'style', 'width' ) as $prop ) {
- $val = sanitize_css_value( $supports_border[ $prop ] ?? '' );
- if ( '' !== $val ) {
- $modal_decls[] = 'border-' . $prop . ':' . $val;
- }
- }
- // Per-side border (split BorderBoxControl output).
- foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) {
- if ( ! isset( $supports_border[ $side ] ) || ! is_array( $supports_border[ $side ] ) ) {
- continue;
- }
- foreach ( array( 'color', 'style', 'width' ) as $prop ) {
- $val = sanitize_css_value( $supports_border[ $side ][ $prop ] ?? '' );
- if ( '' !== $val ) {
- $modal_decls[] = 'border-' . $side . '-' . $prop . ':' . $val;
- }
- }
- }
- // Border radius.
- $radius_val = $supports_border['radius'] ?? null;
- $modal_decls = array_merge( $modal_decls, build_radius_decls( $radius_val ) );
-
- if ( $modal_decls ) {
- // overflow:hidden clips content to the border-radius.
- $rules[] = $scope . ' .donations__modal-dialog{' . implode( ';', $modal_decls ) . ';overflow:hidden}';
- }
-
- // Trigger button alignment reuses the existing contentAlignment attribute.
if ( in_array( $content_alignment, array( 'left', 'center', 'right' ), true ) ) {
$rules[] = $scope . '{text-align:' . $content_alignment . '}';
}
diff --git a/projects/plugins/jetpack/extensions/blocks/donations/edit.js b/projects/plugins/jetpack/extensions/blocks/donations/edit.js
index 31016e5aded..8e47bfdda43 100644
--- a/projects/plugins/jetpack/extensions/blocks/donations/edit.js
+++ b/projects/plugins/jetpack/extensions/blocks/donations/edit.js
@@ -12,6 +12,7 @@ import useIsUserConnected from '../../shared/use-is-user-connected';
import { store as membershipProductsStore } from '../../store/membership-products';
import { STORE_NAME as MEMBERSHIPS_PRODUCTS_STORE } from '../../store/membership-products/constants';
import buildCustomStyles from './build-custom-styles';
+import Controls from './controls';
import fetchDefaultProducts from './fetch-default-products';
import fetchStatus from './fetch-status';
import FirstTimeModal from './first-time-modal';
@@ -212,16 +213,19 @@ const Edit = props => {
const triggerIconEntry = TRIGGER_ICONS.find( ( { key } ) => key === triggerIcon );
const triggerLabel = triggerButtonText || __( 'Support me', 'jetpack' );
content = (
-
+ <>
+
+
+ >
);
} else {
content = ;
From ccba0734dcb1dc249a5da0b52e29b4f761e1aa84 Mon Sep 17 00:00:00 2001
From: Angela Blake
Date: Wed, 6 May 2026 11:10:42 -0500
Subject: [PATCH 07/11] Donations Pop-up mode: hide border controls, add manual
block border for In-page mode
Remove __experimentalBorder from block.json supports (which auto-serialized border
onto the full block wrapper in both modes). Add blockBorder/blockBorderRadius attributes
manually via custom BorderBoxControl + BorderRadiusControl in controls.js, rendered only
when displayMode !== 'modal'. Border rules emit via the per-instance