Groups

Groups organize related fields together, creating a logical structure for both the UI and the stored data.


Basic Groups

$stack->group('address', function ($group) {
    $group->field('street', ['type' => 'text', 'label' => 'Street Address']);
    $group->field('city', ['type' => 'text', 'label' => 'City']);
    $group->field('state', ['type' => 'text', 'label' => 'State']);
    $group->field('zip', ['type' => 'text', 'label' => 'ZIP Code']);
}, ['label' => 'Address']);

Data Structure:

[
    'address' => [
        'street' => '123 Main St',
        'city' => 'New York',
        'state' => 'NY',
        'zip' => '10001',
    ]
]

Group Configuration

Groups support the following configuration options:

$stack->group('group_key', function ($group) {
    // Define fields here
}, [
    // Display options
    'label' => 'Group Label',           // Display label
    'description' => 'Help text',       // Description shown below label
    
    // Layout options
    'layout' => 'box',                  // 'inline' (default) or 'box'
    'collapsible' => true,              // Allow collapsing the group
    
    // Repeatable options
    'repeatable' => true,               // Allow multiple instances
    'min_items' => 1,                   // Minimum items (repeatable)
    'max_items' => 10,                  // Maximum items (repeatable)
    
    // Modal/Deferred options
    'deferred' => true,                 // Show in modal instead of inline
    'ui' => [
        'triggerLabel' => 'Configure',  // Button label
        'render' => 'modal',            // 'modal', 'drawer', or 'panel'
    ],
    
    // Conditional visibility
    'conditions' => [
        ['field' => 'enable_feature', 'operator' => '==', 'value' => true],
    ],
]);

Configuration Options

OptionTypeDescription
labelstringDisplay label for the group
descriptionstringHelp text shown below label
layoutstring'inline' (default) or 'box'
collapsibleboolAllow collapsing the group
repeatableboolAllow multiple instances
min_itemsintMinimum items for repeatable groups
max_itemsintMaximum items for repeatable groups
deferredboolShow in modal instead of inline
uiarrayUI options for deferred groups
conditionsarrayConditional visibility rules

Nested Groups

Groups can be nested inside other groups:

$stack->group('company', function ($group) {
    $group->field('name', ['type' => 'text', 'label' => 'Company Name']);
    $group->field('email', ['type' => 'email', 'label' => 'Email']);
    
    // Nested group for address
    $group->group('address', function ($nested) {
        $nested->field('street', ['type' => 'text', 'label' => 'Street']);
        $nested->field('city', ['type' => 'text', 'label' => 'City']);
        $nested->field('country', [
            'type' => 'select',
            'label' => 'Country',
            'options' => [
                ['value' => 'US', 'label' => 'United States'],
                ['value' => 'CA', 'label' => 'Canada'],
                ['value' => 'UK', 'label' => 'United Kingdom'],
            ],
        ]);
    }, ['label' => 'Company Address']);
    
}, ['label' => 'Company Information']);

Data Structure:

[
    'company' => [
        'name' => 'Acme Inc',
        'email' => 'contact@acme.com',
        'address' => [
            'street' => '123 Business Ave',
            'city' => 'San Francisco',
            'country' => 'US',
        ]
    ]
]

Repeatable Groups

Repeatable groups allow users to add multiple instances of a set of fields:

$stack->group('team_members', function ($group) {
    $group->field('name', ['type' => 'text', 'label' => 'Name']);
    $group->field('role', ['type' => 'text', 'label' => 'Role']);
    $group->field('photo', ['type' => 'media', 'label' => 'Photo']);
    $group->field('bio', ['type' => 'textarea', 'label' => 'Bio']);
}, [
    'label' => 'Team Members',
    'repeatable' => true,
    'min_items' => 1,
    'max_items' => 20,
]);

Data Structure (Array of items):

[
    'team_members' => [
        [
            'name' => 'John Doe',
            'role' => 'CEO',
            'photo' => 123,
            'bio' => 'John is the founder...',
        ],
        [
            'name' => 'Jane Smith',
            'role' => 'CTO',
            'photo' => 124,
            'bio' => 'Jane leads our tech team...',
        ],
    ]
]

Repeatable Group Options

OptionTypeDefaultDescription
repeatableboolfalseEnable repeatable mode
min_itemsint0Minimum number of items
max_itemsintnullMaximum number of items (null = unlimited)

Collapsible Groups

Collapsible groups can be expanded/collapsed to reduce UI clutter:

$stack->group('advanced_settings', function ($group) {
    $group->field('cache_duration', ['type' => 'number', 'label' => 'Cache Duration']);
    $group->field('debug_mode', ['type' => 'toggle', 'label' => 'Debug Mode']);
    $group->field('custom_code', ['type' => 'code', 'label' => 'Custom Code']);
}, [
    'label' => 'Advanced Settings',
    'collapsible' => true,
    'layout' => 'box',
]);

Conditional Groups

Groups can be shown/hidden based on other field values:

// Enable toggle
$stack->field('enable_shipping', [
    'type' => 'toggle',
    'label' => 'Enable Shipping',
    'default' => false,
]);
 
// Conditional group - only shows when shipping is enabled
$stack->group('shipping', function ($group) {
    $group->field('method', [
        'type' => 'select',
        'label' => 'Shipping Method',
        'options' => [
            ['value' => 'flat', 'label' => 'Flat Rate'],
            ['value' => 'free', 'label' => 'Free Shipping'],
            ['value' => 'calculated', 'label' => 'Calculated'],
        ],
    ]);
    $group->field('cost', ['type' => 'number', 'label' => 'Shipping Cost']);
}, [
    'label' => 'Shipping Options',
    'conditions' => [
        ['field' => 'enable_shipping', 'operator' => '==', 'value' => true],
    ],
]);

Retrieving Group Data

use OptStack\OptStack;
 
// Simple group field
$street = OptStack::getField('settings', 'address.street', '');
 
// Nested group field
$city = OptStack::getField('settings', 'company.address.city', '');
 
// Repeatable group (get all items)
$members = OptStack::getField('settings', 'team_members', []);
 
// Loop through repeatable items
foreach ($members as $member) {
    echo $member['name'] . ' - ' . $member['role'];
}
 
// For post meta
$price = OptStack::getField('product_data', 'pricing.regular_price', 0, $post_id);

Best Practices

1. Group Related Fields

// ✅ Good: Related fields grouped together
$stack->group('contact', function ($group) {
    $group->field('email', [...]);
    $group->field('phone', [...]);
    $group->field('address', [...]);
}, ['label' => 'Contact Information']);
 
// ❌ Bad: Unrelated fields in same group
$stack->group('misc', function ($group) {
    $group->field('email', [...]);
    $group->field('theme_color', [...]);
    $group->field('enable_cache', [...]);
});

2. Limit Nesting Depth

// ✅ Good: Max 2 levels of nesting
$stack->group('company', function ($group) {
    $group->group('address', function ($nested) {
        $nested->field('street', [...]);
        $nested->field('city', [...]);
    });
});
 
// ❌ Bad: Too deeply nested
$stack->group('a', function ($g) {
    $g->group('b', function ($g) {
        $g->group('c', function ($g) {
            $g->group('d', function ($g) {...});
        });
    });
});

3. Use Descriptive Keys

// ✅ Good: Descriptive, readable keys
$stack->group('shipping_options', function ($group) {
    $group->field('enable_free_shipping', [...]);
    $group->field('free_shipping_threshold', [...]);
});
 
// ❌ Bad: Cryptic or unclear keys
$stack->group('so', function ($group) {
    $group->field('efs', [...]);
    $group->field('fst', [...]);
});