Skip to content

[MOOSE-403] Comparison Table Block - #363

Open
GeoffDusome wants to merge 18 commits into
mainfrom
feature/MOOSE-403/comparison-table-block
Open

[MOOSE-403] Comparison Table Block#363
GeoffDusome wants to merge 18 commits into
mainfrom
feature/MOOSE-403/comparison-table-block

Conversation

@GeoffDusome

@GeoffDusome GeoffDusome commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

What does this do/fix?

Note

Design would like to see this block in ModernPress so that's why the PR is here and not in MP Resources.

Adds a new Comparison Table block (tribe/comparison-table) for building feature comparison matrices — pricing tiers, product plans, and similar layouts.

Blocks

  • tribe/comparison-table — Parent block with configurable plan columns and table-wide settings.
  • tribe/comparison-row — Child block for category rows (section headers) and feature rows (comparison cells). Uses block context to stay in sync with parent columns.

Column configuration

Each plan column supports:

  • Label and subtitle (e.g. plan name and price)
  • Optional badge (rendered with existing tag styles)
  • Optional CTA (label, URL, button style, open in new tab)

Cell types

Feature rows support three cell types per column:

  • Check — included (circle-check icon)
  • Dash — not included (circle-cross icon)
  • Text — custom value (e.g. "25", "Unlimited")

Table settings

  • Toggle CTAs on/off
  • CTA placement: table footer or table header
  • Mobile card layout — switches from scrollable table to stacked plan cards on small screens
  • Mobile card carousel — optional Swiper carousel (one plan at a time with pagination)

Editor experience

  • Visual table preview with sortable column chips
  • Drag-and-drop reordering of rows (categories and features move together as sections)
  • Per-row and per-column inspector panels
  • Block example with starter content for quick insertion

Frontend

  • Semantic <table> markup with accessible scroll region on desktop
  • Alternating row styling for feature rows
  • Mobile card layout mirrors desktop content (categories, features, CTAs)
  • Swiper integration for mobile carousel (view.js)
  • Supports custom alignments: wide, grid, full

Backend

  • Comparison_Table_Block_Controller and Comparison_Row_Block_Controller handle server-side rendering via render.php
  • New SVG icons registered in the icon system (circle-check, circle-cross)
  • Blocks registered in Blocks_Definer.php and theme.json

QA

Test Plan:

  • Insert a Comparison Table block and confirm the example content loads (3 columns, category row, feature rows)
  • Add, edit, and remove plan columns; verify column chip sorting updates the table
  • Edit column fields: label, subtitle, badge, CTA label/URL/style, open in new tab
  • Add category and feature rows; confirm category rows span the full table width
  • Set cell types (check, dash, text) per column on feature rows; confirm values persist when switching types
  • Drag-and-drop rows to reorder; confirm category sections move with their child features
  • Toggle Show CTAs off/on; confirm CTAs disappear/reappear
  • Switch CTA placement between Table footer and Table header; confirm correct position on desktop and mobile cards
  • Enable Mobile card layout; resize to mobile and confirm cards render with correct plan data
  • Enable Mobile card carousel; confirm Swiper pagination works and only one card is visible at a time
  • Test custom alignments (wide, grid, full)
  • Verify check/dash icons and badge tag styling match design
  • Save and reload the editor; confirm all attributes and inner blocks persist
  • View on the frontend at desktop and mobile breakpoints

Links to relevant issues

Demo Environment:

Screenshots/video:

Pull request checklist

  • I've added a changelog entry for these changes.
  • I've linked to a relevant Jira issue.
  • I've captured a screenshot or screencast of the changes and linked it above.

@GeoffDusome GeoffDusome self-assigned this Jun 5, 2026

public function render_table_column_header( int $index ): string {
$subtitle = $this->get_column_subtitle( $index );
$subtitle_markup = '' !== $subtitle

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a blocker, but we should keep an eye on such comparisons.
For some reason, AI keeps doing a lot of redundant getters/setters and uses them only in the scope of the class they defined. But the hard-to-read things like this comparison and the one below, it keeps as a ternary operation
Moreover, it doesn't re-use that logic and keeps duplication here and in lines below

@MlKilderkin MlKilderkin Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe both cta_markup and subtitle_markup should be a reimplemented via re-usabled methods with 2 params: tag and type of entity or full class name. Currently they are the same

/**
* @return array<int, \Tribe\Plugin\Components\Blocks\Comparison_Row_Block_Controller>
*/
public function get_row_controllers(): array {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bad solution but maybe we can add some "caching" here? Example:

	/**
	 * Lazily built and cached by get_row_controllers().
	 *
	 * @var array<int, \Tribe\Plugin\Components\Blocks\Comparison_Row_Block_Controller>|null
	 */
	protected ?array $row_controllers = null;

public function get_row_controllers(): array {
		if ( null === $this->row_controllers ) {
			$this->row_controllers = $this->build_row_controllers();
		}

		return $this->row_controllers;
	}

/**
	 * @return array<int, \Tribe\Plugin\Components\Blocks\Comparison_Row_Block_Controller>
	 */
	protected function build_row_controllers(): array {
		$columns     = $this->columns;
		$controllers = [];

		foreach ( $this->rows as $row ) {
			$controllers[] = Comparison_Row_Block_Controller::factory( [
				'attributes'    => $row,
				'columns'       => $columns,
				'block_classes' => 'wp-block-tribe-comparison-row',
			] );
		}

		return $controllers;
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants