Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions src/Customize/LayoutSection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
*
* The Layout section is the design system's page-anatomy + rhythm contract:
* the site container width, the content inset (reading measure), the rail scale
* (sidebar widths), and the spacing level (vertical rhythm). It absorbed the
* former Spacing section on 2026-07-23 — container and inset are one page
* (sidebar widths), the rail gap (content-to-rail gutter), and the spacing
* level (vertical rhythm). It absorbed the former Spacing section on
* 2026-07-23 — container and inset are one page
* anatomy, so a single section reads as one coherent story.
*
* @since 2.4.0
Expand Down Expand Up @@ -262,6 +263,32 @@ protected function add_style_manager_section_layout_config( array $config ): arr
],
],
],
// Content-to-rail gutter: independent from both the rail widths and
// the global rhythm. The value is a multiplier consumed by Nova Blocks
// against its fluid spacing token. A default of 2 preserves the
// pre-control Sidecar geometry byte-for-byte.
'sm_rail_gap' => [
'type' => 'range',
'setting_type' => 'option',
'setting_id' => 'sm_rail_gap',
'live' => true,
'label' => esc_html__( 'Rail Gap', '__plugin_txtd' ),
'desc' => esc_html__( 'Adjust the distance between the main content and adjacent sidebars without changing their widths or the site rhythm.', '__plugin_txtd' ),
'default' => 2,
'input_attrs' => [
'min' => 1,
'max' => 2.5,
'step' => 0.25,
'data-preview' => true,
],
'css' => [
[
'property' => '--sm-rail-gap',
'selector' => ':root',
'unit' => '',
],
],
],
// Vertical rhythm: the multiplication factor for the distance between
// elements. Moved here from the merged Spacing section (config is
// byte-identical).
Expand Down Expand Up @@ -309,13 +336,14 @@ protected function reorganize_customizer_controls( array $sm_panel_config, array

// The page-anatomy story, top to bottom: how wide the container is, how far
// content is inset, how wide the rails are (presets face + base slider span),
// then the vertical rhythm between elements.
// the content-to-rail gutter, then the vertical rhythm between elements.
$layout_section_fields = [
'sm_site_container_width',
'sm_content_inset',
'sm_rail_scale_preset',
'sm_rail_scale',
'sm_rail_pitch',
'sm_rail_gap',
'sm_spacing_level',
];

Expand Down
62 changes: 62 additions & 0 deletions tests/phpunit/Unit/Customize/LayoutSectionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
declare ( strict_types = 1 );

namespace Pixelgrade\StyleManager\Tests\Unit\Customize;

use Brain\Monkey\Functions;
use Pixelgrade\StyleManager\Customize\LayoutSection;
use Pixelgrade\StyleManager\Tests\Unit\TestCase;

class LayoutSectionTest extends TestCase {
private TestLayoutSection $section;

public function setUp(): void {
parent::setUp();

Functions\when( 'apply_filters' )->returnArg( 2 );
Functions\when( 'esc_html__' )->returnArg( 1 );

$this->section = new TestLayoutSection();
}

public function test_layout_defines_an_independent_rail_gap_token_control(): void {
$config = $this->section->expose_add_style_manager_section_layout_config( [] );
$options = $config['sections']['style_manager_section']['options'];
$control = $options['sm_rail_gap'];

$this->assertSame( 'range', $control['type'] );
$this->assertSame( 'option', $control['setting_type'] );
$this->assertSame( 'sm_rail_gap', $control['setting_id'] );
$this->assertTrue( $control['live'] );
$this->assertSame( 'Rail Gap', $control['label'] );
$this->assertSame( 2, $control['default'] );
$this->assertSame(
[ 'min' => 1, 'max' => 2.5, 'step' => 0.25, 'data-preview' => true ],
$control['input_attrs']
);
$this->assertSame( '--sm-rail-gap', $control['css'][0]['property'] );
$this->assertSame( ':root', $control['css'][0]['selector'] );
$this->assertSame( '', $control['css'][0]['unit'] );
}

public function test_layout_places_rail_gap_between_rail_pitch_and_global_spacing(): void {
$config = $this->section->expose_add_style_manager_section_layout_config( [] );
$options = $config['sections']['style_manager_section']['options'];
$panel = $this->section->expose_reorganize_customizer_controls( [], $config['sections']['style_manager_section'] );
$keys = array_keys( $panel['sections']['sm_layout_section']['options'] );

$this->assertArrayHasKey( 'sm_rail_gap', $options );
$this->assertSame( 'sm_rail_pitch', $keys[ array_search( 'sm_rail_gap', $keys, true ) - 1 ] );
$this->assertSame( 'sm_spacing_level', $keys[ array_search( 'sm_rail_gap', $keys, true ) + 1 ] );
}
}

class TestLayoutSection extends LayoutSection {
public function expose_add_style_manager_section_layout_config( array $config ): array {
return $this->add_style_manager_section_layout_config( $config );
}

public function expose_reorganize_customizer_controls( array $panel_config, array $section_config ): array {
return $this->reorganize_customizer_controls( $panel_config, $section_config );
}
}
Loading