Skip to content

Component System Overview

LamaPress uses a component-based architecture that promotes reusability, maintainability, and separation of concerns. This guide explains how the component system works and how to create and use components effectively.

Table of Contents

Component Types

LamaPress organizes components into three main categories:

1. Sections (components/sections/)

Full-width page sections that typically span the entire viewport width. Examples:

  • Hero sections
  • Content sections
  • Image galleries
  • Video sections

Characteristics:

  • Full-width layout
  • Usually have ACF field groups
  • Can be used in flexible content layouts
  • Often have their own container and spacing

Example: components/sections/hero_basic/

2. Blocks (components/blocks/)

Reusable UI components that can be placed anywhere in a layout. Examples:

  • Accordions
  • Image sliders
  • Rich content blocks
  • Video players

Characteristics:

  • Self-contained components
  • Can be nested within sections
  • Reusable across different contexts
  • May or may not have ACF fields

Example: components/blocks/accordion/

3. Parts (components/parts/)

Small, atomic components used for specific UI elements. Examples:

  • Buttons
  • Icons
  • Images
  • Form inputs

Characteristics:

  • Small, focused components
  • Highly reusable
  • Often used within blocks and sections
  • Minimal or no ACF fields

Example: components/parts/buttons/default/

Component Structure

Each component follows a consistent directory structure:

components/
├── sections/
│   └── hero_basic/
│       ├── index.php      # Required: Main template
│       ├── acf.php        # Required: ACF field definitions
│       ├── index.js       # Optional: JavaScript class
│       ├── style.scss     # Optional: Component styles
│       └── functions.php  # Optional: Component functions

├── blocks/
│   └── accordion/
│       ├── index.php      # Required: Main template
│       ├── acf.php        # Optional: ACF fields (if reusable)
│       ├── index.js       # Optional: JavaScript class
│       └── style.scss     # Optional: Component styles

└── parts/
    └── buttons/
        └── default/
            ├── index.php  # Required: Main template
            ├── index.js   # Optional: JavaScript class
            └── style.scss # Optional: Component styles

Required Files

index.php (Required)

The main template file that renders the component. This file receives data through variables extracted from the $props array.

Sections:

php
<?php
// Variables are available from ACF fields via $key
$title = llField('title', $key);
$content = llField('content', $key);
?>

<section class="ll-section ll-section--hero">
    <div class="ll-container">
        <h1><?= esc_html($title) ?></h1>
        <div><?= wp_kses_post($content) ?></div>
    </div>
</section>

Blocks and Parts:

php
<?php
// Properties are passed directly via $props
$title = $fields['title'] ?? false;
$content = $fields['content'] ?? false;
$classes = $classes ?? '';
?>

<div class="ll-block--accordion <?= $classes ?>" data-component="blocks/accordion">
    <?php if ($title): ?>
        <h2><?= esc_html($title) ?></h2>
    <?php endif; ?>
</div>

Optional Files

acf.php (Required for sections, optional for blocks/parts)

Defines Advanced Custom Fields for the component.

Sections:

php
<?php
$key = 'section_hero_basic';
$name = 'hero_basic';
$title = 'Hero Basic';

$groupFields = [
    [
        'key' => 'title',
        'label' => 'Title',
        'name' => 'title',
        'type' => 'text',
    ],
    [
        'key' => 'content',
        'label' => 'Content',
        'name' => 'content',
        'type' => 'wysiwyg',
    ],
];

Blocks/Parts:

php
<?php
// Only return $groupFields array, no $key, $name, or $title
$groupFields = [
    [
        'key' => 'title',
        'label' => 'Title',
        'name' => 'title',
        'type' => 'text',
    ],
];

index.js (Optional)

JavaScript class for component interactivity. Automatically loaded when data-component attribute is present.

javascript
export default class Accordion {
  constructor(element) {
    this.element = element
    this.init()
  }

  init = () => {
    // Initialization logic
    this.bindEvents()
  }

  bindEvents = () => {
    // Event listeners
  }

  enter = () => {
    // Animation when component enters viewport
  }

  destroy = () => {
    // Cleanup when component is destroyed
  }
}

style.scss (Optional)

Component-specific styles. Only add if Tailwind classes aren't sufficient.

scss
.ll-block--accordion {
  // Component-specific styles
}

functions.php (Optional, sections only)

Component-specific PHP functions.

Component Loading

Components are loaded using helper functions defined in functions/includes.php:

Loading Sections

php
<?php
// In a template file
llSection('hero_basic', 'hero_section_1');

Parameters:

  • $name - Section name (directory name)
  • $key - Unique key for ACF field group

Loading Blocks

php
<?php
llBlock('accordion', [
    'fields' => [
        'title' => 'FAQ Section',
        'items' => $accordion_items
    ],
    'classes' => 'my-custom-class'
]);

Parameters:

  • $name - Block name (directory name)
  • $props - Array containing:
    • fields - Field data for the component
    • classes - Additional CSS classes

Loading Parts

php
<?php
llPart('buttons/default', [
    'text' => 'Click Me',
    'url' => '/contact',
    'classes' => 'll-btn-primary'
]);

Parameters:

  • $name - Part name (can include subdirectories)
  • $props - Array of properties to pass to the component

Data Flow

Understanding how data flows from ACF to components:

Sections

  1. ACF Fields → Defined in acf.php
  2. Template Registration → Automatically registered via llSections()
  3. Field Retrieval → Accessed via llField() with $key prefix
  4. Template Renderingindex.php receives data through variables
php
// In section template
$title = llField('title', $key); // Gets 'hero_section_1_title'
$content = llField('content', $key); // Gets 'hero_section_1_content'

Blocks and Parts

  1. Props Passed → Data passed directly via llBlock() or llPart()
  2. Variable Extractionextract($props) makes variables available
  3. Template Renderingindex.php uses the extracted variables
php
// When calling
llBlock('accordion', [
    'fields' => ['title' => 'Hello'],
    'classes' => 'custom'
]);

// In block template
$fields = $fields ?? []; // Available from props
$classes = $classes ?? ''; // Available from props

Creating Components

Step-by-Step: Creating a Block Component

  1. Create Directory Structure

    bash
    mkdir -p components/blocks/my-component
  2. Create index.php

    php
    <?php
    $classes = $classes ?? '';
    $title = $fields['title'] ?? false;
    ?>
    
    <div class="ll-block--my-component <?= $classes ?>" data-component="blocks/my-component">
        <?php if ($title): ?>
            <h2><?= esc_html($title) ?></h2>
        <?php endif; ?>
    </div>
  3. Create acf.php (if needed)

    php
    <?php
    $groupFields = [
        [
            'key' => 'title',
            'label' => 'Title',
            'name' => 'title',
            'type' => 'text',
        ],
    ];
  4. Create index.js (if needed)

    javascript
    export default class MyComponent {
      constructor(element) {
        this.element = element
        this.init()
      }
    
      init = () => {
        this.bindEvents()
      }
    
      bindEvents = () => {
        // Event listeners
      }
    
      enter = () => {
        // Reveal animation
      }
    
      destroy = () => {
        // Cleanup
      }
    }
  5. Use the Component

    php
    <?php
    llBlock('my-component', [
        'fields' => [
            'title' => 'My Component Title'
        ]
    ]);
    ?>

JavaScript Integration

Automatic Component Loading

Components with JavaScript are automatically initialized when:

  1. The component has a data-component attribute
  2. A corresponding index.js file exists
  3. The JavaScript file exports a default class

Example:

html
<div data-component="blocks/accordion">
  <!-- Component content -->
</div>

The system will automatically:

  1. Find components/blocks/accordion/index.js
  2. Import the default class
  3. Instantiate it with the element
  4. Call lifecycle methods (init, enter, etc.)

Component Lifecycle

Components follow a consistent lifecycle:

  1. Constructor - Receives the DOM element
  2. init() - Initialization (called automatically)
  3. bindEvents() - Set up event listeners (called from init)
  4. enter() - Reveal animation (called when component enters viewport)
  5. destroy() - Cleanup (called when component is destroyed)

Accessing Other Components

Components can access other component instances through the app:

javascript
import { app } from '@src/js/core/app'

export default class MyComponent {
  init = () => {
    // Access another component instance
    const accordion = app.instances.get('page').instances.get(accordionElement)
  }
}

Best Practices

1. Naming Conventions

  • Use lowercase with underscores: hero_basic, rich_content
  • Be descriptive: image_slider not slider
  • Follow existing patterns in the codebase

2. File Organization

  • Keep components focused and single-purpose
  • Only add files you actually need
  • Don't create empty acf.php files if no fields are needed

3. Data Handling

  • Always use null coalescing (??) for optional fields
  • Escape output: esc_html() for text, wp_kses_post() for HTML
  • Validate data before using it

4. CSS Classes

  • Prefix custom classes with ll-
  • Use Tailwind classes when possible
  • Only add custom SCSS when necessary

5. JavaScript

  • Use arrow functions for methods
  • Bind timelines to component instance
  • Always implement destroy() for cleanup
  • Use data-component attribute for automatic loading

6. ACF Fields

  • Use descriptive field keys and names
  • Follow ACF best practices
  • Group related fields logically
  • Only create acf.php for reusable field groups

Common Patterns

Pattern 1: Conditional Rendering

php
<?php
$title = $fields['title'] ?? false;
$content = $fields['content'] ?? false;
?>

<?php if ($title || $content): ?>
    <div class="ll-block--content">
        <?php if ($title): ?>
            <h2><?= esc_html($title) ?></h2>
        <?php endif; ?>
        
        <?php if ($content): ?>
            <div><?= wp_kses_post($content) ?></div>
        <?php endif; ?>
    </div>
<?php endif; ?>

Pattern 2: Looping Through Items

php
<?php
$items = $fields['items'] ?? [];
?>

<?php if ($items): ?>
    <div class="ll-block--list">
        <?php foreach ($items as $item): ?>
            <div class="ll-block--list-item">
                <?= esc_html($item['title']) ?>
            </div>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

Pattern 3: Nested Components

php
<?php
$items = $fields['items'] ?? [];
?>

<?php if ($items): ?>
    <div class="ll-block--accordion" data-component="blocks/accordion">
        <?php foreach ($items as $item): ?>
            <?php llBlock('accordion_item', ['fields' => $item]); ?>
        <?php endforeach; ?>
    </div>
<?php endif; ?>

Next Steps:

Released under the MIT License.