Skip to content

Common Patterns

This guide covers common development patterns and recipes used in LamaPress projects. These patterns solve frequent problems and demonstrate best practices.

Table of Contents

Component Patterns

Pattern 1: Conditional Component Rendering

Only render component if data exists:

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 Repeater Fields

Loop through ACF repeater fields:

php
<?php
$items = llField('items', $key) ?? [];
?>

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

Pattern 3: Nested Components

Compose components from other 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; ?>

Pattern 4: Image with Fallback

Display image with fallback:

php
<?php
$image = $fields['image'] ?? false;
$fallback = llPublic('images/placeholder.jpg');
$imageUrl = $image ? $image['url'] : $fallback;
$imageAlt = $image ? $image['alt'] : 'Placeholder';
?>

<img src="<?= esc_url($imageUrl) ?>" alt="<?= esc_attr($imageAlt) ?>">

Create reusable link component:

php
<?php
$url = $fields['url'] ?? '#';
$text = $fields['text'] ?? 'Click here';
$target = $fields['target'] ?? '_self';
?>

<a href="<?= esc_url($url) ?>" target="<?= esc_attr($target) ?>" class="ll-link">
    <?= esc_html($text) ?>
</a>

JavaScript Patterns

Pattern 1: Hover Animation

Create hover animations with GSAP:

javascript
import { gsap } from 'gsap/all'
import { killTimeline } from '@src/js/core/utils/helpers'

export default class HoverCard {
  constructor(element) {
    this.element = element
    this.hoverTimeline = null
    this.init()
  }

  init = () => {
    this.bindEvents()
  }

  bindEvents = () => {
    this.element.addEventListener('mouseenter', this.handleMouseEnter)
    this.element.addEventListener('mouseleave', this.handleMouseLeave)
  }

  handleMouseEnter = () => {
    killTimeline(this.hoverTimeline)
    this.hoverTimeline = gsap.timeline()
    this.hoverTimeline.to(this.element, {
      scale: 1.05,
      y: -5,
      duration: 0.3,
      ease: 'power2.out'
    })
  }

  handleMouseLeave = () => {
    killTimeline(this.hoverTimeline)
    this.hoverTimeline = gsap.timeline()
    this.hoverTimeline.to(this.element, {
      scale: 1,
      y: 0,
      duration: 0.3,
      ease: 'power2.out'
    })
  }

  destroy = () => {
    killTimeline(this.hoverTimeline)
    this.element.removeEventListener('mouseenter', this.handleMouseEnter)
    this.element.removeEventListener('mouseleave', this.handleMouseLeave)
  }
}

Pattern 2: Scroll-Triggered Animation

Reveal animation on scroll:

javascript
import { gsap } from 'gsap/all'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { killTimeline } from '@src/js/core/utils/helpers'

export default class RevealBlock {
  constructor(element) {
    this.element = element
    this.timeline = null
    this.init()
  }

  init = () => {
    this.createRevealAnimation()
  }

  createRevealAnimation = () => {
    this.timeline = gsap.timeline({
      scrollTrigger: {
        trigger: this.element,
        start: 'top 80%',
        once: true,
      }
    })
    
    this.timeline.from(this.element, {
      opacity: 0,
      y: 30,
      duration: 0.8,
      ease: 'power3.out'
    })
  }

  destroy = () => {
    killTimeline(this.timeline)
    ScrollTrigger.getAll().forEach(trigger => {
      if (trigger.vars.trigger === this.element) {
        trigger.kill()
      }
    })
  }
}

Pattern 3: Accordion Component

Interactive accordion pattern:

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

export default class AccordionItem {
  constructor(element) {
    this.element = element
    this.button = this.element.querySelector('.js-accordion-button')
    this.content = this.element.querySelector('.js-accordion-content')
    this.isOpen = false
    this.timeline = null
    this.init()
  }

  init = () => {
    this.bindEvents()
    this.setInitialState()
  }

  setInitialState = () => {
    gsap.set(this.content, { height: 0, overflow: 'hidden' })
  }

  bindEvents = () => {
    this.button.addEventListener('click', this.toggle)
  }

  toggle = () => {
    if (this.isOpen) {
      this.close()
    } else {
      this.open()
    }
  }

  open = () => {
    this.isOpen = true
    this.element.classList.add('is-open')
    
    this.timeline = gsap.timeline()
    this.timeline.to(this.content, {
      height: 'auto',
      duration: 0.4,
      ease: 'power2.out'
    })
  }

  close = () => {
    this.isOpen = false
    this.element.classList.remove('is-open')
    
    this.timeline = gsap.timeline()
    this.timeline.to(this.content, {
      height: 0,
      duration: 0.4,
      ease: 'power2.in'
    })
  }

  destroy = () => {
    this.button.removeEventListener('click', this.toggle)
  }
}

Pattern 4: Accessing Child Components

Access child component instances:

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

export default class ParentComponent {
  constructor(element) {
    this.element = element
    this.childElements = [...this.element.querySelectorAll('[data-component="blocks/child"]')]
    this.init()
  }

  init = () => {
    this.bindEvents()
  }

  bindEvents = () => {
    this.element.addEventListener('click', this.handleClick)
  }

  handleClick = (e) => {
    const childElement = e.target.closest('[data-component="blocks/child"]')
    if (childElement) {
      const childInstance = app.instances.get('page').instances.get(childElement)
      if (childInstance && childInstance.doSomething) {
        childInstance.doSomething()
      }
    }
  }
}

ACF Patterns

Pattern 1: Conditional Field Display

Show/hide fields based on other fields:

php
<?php
$groupFields = [
    [
        'key' => $name . '_01',
        'label' => 'Show Advanced',
        'name' => 'show_advanced',
        'type' => 'true_false',
    ],
    [
        'key' => $name . '_02',
        'label' => 'Advanced Content',
        'name' => 'advanced_content',
        'type' => 'wysiwyg',
        'conditional_logic' => [
            [
                [
                    'field' => $name . '_01',
                    'operator' => '==',
                    'value' => '1',
                ],
            ],
        ],
    ],
];

Pattern 2: Reusable Field Group

Create reusable field groups:

php
<?php
// In block/part acf.php
$groupFields = [
    [
        'key' => 'title',
        'label' => 'Title',
        'name' => 'title',
        'type' => 'text',
    ],
    [
        'key' => 'content',
        'label' => 'Content',
        'name' => 'content',
        'type' => 'wysiwyg',
    ],
];

Reuse in section:

php
<?php
$reusableFields = llGetFields('block', 'content_block', $key);
$groupFields = array_merge(
    $reusableFields,
    [
        // Additional section-specific fields
    ]
);

Pattern 3: Image Field with Sizes

Use WordPress image sizes:

php
<?php
$image = llField('image', $key);
if ($image) {
    $imageUrl = wp_get_attachment_image_url($image['ID'], 'large');
    $imageAlt = $image['alt'] ?? '';
}
?>

Styling Patterns

Pattern 1: Responsive Container

Container with responsive padding:

php
<div class="ll-container py-4 md:py-8 lg:py-12">
    <!-- Content -->
</div>

Pattern 2: Grid Layout

Responsive grid layout:

php
<div class="ll-grid">
    <div class="col-span-12 md:col-span-6 lg:col-span-4">
        <!-- Item 1 -->
    </div>
    <div class="col-span-12 md:col-span-6 lg:col-span-4">
        <!-- Item 2 -->
    </div>
    <div class="col-span-12 md:col-span-6 lg:col-span-4">
        <!-- Item 3 -->
    </div>
</div>

Pattern 3: Theme Switching

Switch themes based on context:

php
<?php
$theme = $fields['theme'] ?? 'default';
?>

<section class="ll-theme-<?= esc_attr($theme) ?>">
    <div class="ll-container">
        <!-- Themed content -->
    </div>
</section>

Animation Patterns

Pattern 1: Stagger Animation

Animate multiple elements with stagger:

javascript
import { gsap } from 'gsap/all'

export default class StaggerList {
  constructor(element) {
    this.element = element
    this.items = [...this.element.querySelectorAll('.js-item')]
    this.init()
  }

  init = () => {
    this.createAnimation()
  }

  createAnimation = () => {
    gsap.from(this.items, {
      opacity: 0,
      y: 20,
      duration: 0.6,
      stagger: 0.1,
      ease: 'power2.out'
    })
  }
}

Pattern 2: Parallax Effect

Simple parallax effect:

javascript
import { gsap } from 'gsap/all'
import { ScrollTrigger } from 'gsap/ScrollTrigger'

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

  init = () => {
    gsap.to(this.element, {
      yPercent: -50,
      ease: 'none',
      scrollTrigger: {
        trigger: this.element,
        start: 'top bottom',
        end: 'bottom top',
        scrub: true,
      }
    })
  }
}

Data Patterns

Pattern 1: Getting Multiple Fields

Get multiple fields efficiently:

php
<?php
$data = llFields('title', 'content', 'image', 'cta_text', $key);
// Returns: ['title' => '...', 'content' => '...', 'image' => '...', 'cta_text' => '...']
?>

Pattern 2: Field Mapping

Map field names to different keys:

php
<?php
$data = llFields(
    ['title' => 'heading'],
    ['content' => 'body'],
    ['image' => 'featured_image'],
    $key
);
// Returns: ['heading' => '...', 'body' => '...', 'featured_image' => '...']
?>

Pattern 3: Options Page Data

Get data from options page:

php
<?php
$companyName = get_field('company_name', 'option');
$address = get_field('address', 'option');
?>

Next Steps:

Released under the MIT License.