Larastrap Recipes

Reusable Bootstrap5 components for Laravel

Larastrap provides the ability to define your own custom element, just extending the existing ones and providing some configuration.
This can be paired with Javascript libraries and custom PHP functions to fine-tune the intended behaviour, and reuse across your code.

Share your Larastrap Recipes!


When building an administration interface, it may be useful to define once the common buttons to create, edit or delete elements. Those can be reused across all templates, and can be modified once to keep consistency.

Configuration

[
    'customs' => [
        'ebutton' => [
            'extends' => 'link',
            'params' => [
                'color' => 'info',
                'label' => 'Edit',
            ],
        ],
        'cbutton' => [
            'extends' => 'link',
            'params' => [
                'color' => 'success',
                'label' => 'Create New',
            ],
        ],
        'dbutton' => [
            'extends' => 'link',
            'params' => [
                'color' => 'danger',
                'label' => 'Delete',
            ],
        ],
    ]
]

Code

<x-larastrap::cbutton />
<x-larastrap::ebutton />
<x-larastrap::dbutton />
<a id="link-4619d2622cbfbe3404f209567cc574ad" class="btn btn-success" href="#">Create New</a> <a id="link-2a83654094bcddfafd4c1617d168b182" class="btn btn-info" href="#">Edit</a> <a id="link-7b5cfa721da15b5892e65974ef541380" class="btn btn-danger" href="#">Delete</a>
A simple x-larastrap::text input can be decorated with a Bootstrap icon and marked with the .date CSS class, used to attach Bootstrap Datepicker (or whatever datepicker JS library you prefer) initialization.
Using reviewCallback it is possible to format the value before display.

Configuration

[
    'customs' => [
        'datepicker' => [
            'extends' => 'text',
            'params' => [
                'classes' => ['date'],
                'placeholder' => 'Never',
                'textappend' => '<i class="bi-calendar"></i>',

                'reviewCallback' => function($component, $params) {
                    $params['value'] = ucwords(strftime('%A %d %B %Y', $params['value']));
                    return $params;
                }
            ],
        ],
    ]
]

Code

<x-larastrap::datepicker label="Pick a Date" :value="time()" />

<!-- Inclusion of Bootstrap Datepicker CSS and JS, and initialization -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>

<script>
$('input.date').datepicker({
    format: 'DD dd MM yyyy',
    autoclose: true,
    clearBtn: true,
});
</script>
<div class="row mb-3">
  <label for="input-0436782f833927d032f04bc6b9510f28" class="col-2 col-form-label">Pick a Date</label>
  <div class="col-10">
    <div class="input-group has-validation">
      <input id="input-0436782f833927d032f04bc6b9510f28" type="text" class="date form-control" name="" value="Friday 24 March 2023" placeholder="Never">
    </div>
  </div>
</div><!-- Inclusion of Bootstrap Datepicker CSS and JS, and initialization -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script> 
<script>

$('input.date').datepicker({
    format: 'DD dd MM yyyy',
    autoclose: true,
    clearBtn: true,
});
</script>
It may be useful to append an icon by default to a x-larastrap::button with a specific intent, such as open a x-larastrap::modal window.

Configuration

[
    'customs' => [
        'mbutton' => [
            'extends' => 'button',
            'params' => [
                'color' => 'light',
                'postlabel' => ' <i class="bi-window"></i>',
            ],
        ],
    ]
]

Code

<x-larastrap::mbutton label="Open Modal" triggers_modal="#test-modal" />

<x-larastrap::modal title="Test" id="test-modal">
  <p>Ciao</p>
</x-larastrap::modal>
<button id="button-3891f7af354d17a6450a0afba1276e2a" class="btn btn-light" data-bs-toggle="modal" data-bs-target="#test-modal">Open Modal</button>
<div class="modal fade" id="test-modal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-none">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">
          Test
        </h5>
      </div>
      <div class="modal-body">
        <p>
          Ciao
        </p>
      </div>
      <div class="modal-footer">
        <button id="button-7d346ba4960c9d83a431f407770bf281" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>
With a reviewCallback applied to x-larastrap::tabpane it is possible to dynamically build its HTML label and keep the template clean and readable. In this example, text in the labels is hidden when displaying on mobile - so to have compact and pretty tabs even on small screens.
This is the first tab
This is the second tab

Configuration

[
    'customs' => [
        'mobiletab' => [
            'extends' => 'tabpane',
            'params' => [
                'reviewCallback' => function($component, $params) {
                    if (isset($params['attributes']['icon'])) {
                        $params['label'] = sprintf('<span class="d-none d-md-inline-block me-2">%s</span><i class="%s"></i>',
                            $params['label'],
                            $params['attributes']['icon']
                        );
                    }

                    return $params;
                }
            ],
        ],
    ]
]

Code

<x-larastrap::tabs active="0" classes="mobiletabs">
    <x-larastrap::mobiletab label="First" icon="bi-person">
        This is the first tab
    </x-larastrap::mobiletab>
    <x-larastrap::mobiletab label="Second" icon="bi-people">
        This is the second tab
    </x-larastrap::mobiletab>
</x-larastrap::tabs>
<ul class="mobiletabs nav nav-tabs" id="tabs-417bd063924ce62f2d72fa4d908bad7f" role="tablist">
  <li class="nav-item" role="presentation">
    <button id="tabpane-58496b32183f0f4c4d72c89568298632-0fe79d7d9354b5e6c9a5dc7ee3ab19cb" class="nav-link active" role="tab" data-bs-toggle="tab" data-bs-target="#tabpane-58496b32183f0f4c4d72c89568298632"><span class="d-none d-md-inline-block me-2">First</span></button>
  </li>
  <li class="nav-item" role="presentation">
    <button id="tabpane-542989cca285826ba20344f2686e2c7c-c3e8a1ae5882c569bc20f4bda48bbe6d" class="nav-link" role="tab" data-bs-toggle="tab" data-bs-target="#tabpane-542989cca285826ba20344f2686e2c7c"><span class="d-none d-md-inline-block me-2">Second</span></button>
  </li>
</ul>
<div class="tab-content" id="tabs-417bd063924ce62f2d72fa4d908bad7f_contents">
  <div class="p-3 tab-pane fade show active" id="tabpane-58496b32183f0f4c4d72c89568298632" role="tabpanel">
    This is the first tab
  </div>
  <div class="p-3 tab-pane fade" id="tabpane-542989cca285826ba20344f2686e2c7c" role="tabpanel">
    This is the second tab
  </div>
</div>
What about injecting your Laravel Dusk selectors only when actually running Dusk tests? This can be in particular useful with x-larastrap::checks and x-larastrap::radios, and be able to select with precision your options and test your own combinations.

Configuration

[
    'customs' => [
        'testablechecks' => [
            'extends' => 'checks',
            'params' => [
                'reviewCallback' => function($component, $params) {
                    /*
                        Choose your method to determine if you are within a Dusk test
                    */
                    if (env('DUSK_TESTING')) {
                        $options = $params['options'];
                        $new_options = [];

                        foreach($options as $value => $option) {
                            if (is_object($option)) {
                                if (!isset($option->button_attributes)) {
                                    $option->button_attributes = [];
                                }
                            }
                            else {
                                $option = (object) [
                                    'label' => $option,
                                    'button_attributes' => [],
                                ];
                            }

                            $option->button_attributes['dusk'] = sprintf('%s-%s', $params['name'], $value);
                            $new_options[$value] = $option;
                        }

                        $params['options'] = $new_options;
                    }

                    return $params;
                }
            ],
        ],
    ]
]

Code

<x-larastrap::testablechecks name="choose" label="Choose one" :options="['1' => 'one', '2' => 'two', '3' => 'three']" />
<div class="row mb-3">
  <label for="checks-9c039f94488a48603a0bb776c6a1cea4" class="col-2 col-form-label col-2 col-form-label">Choose one</label>
  <div class="col-10">
    <div class="btn-group" role="group">
      <input type="checkbox" class="btn-check" name="choose[]" id="checks-9c039f94488a48603a0bb776c6a1cea4-f97c5d29941bfb1b2fdab0874906ab82" autocomplete="off" value="1"> <label class="btn btn-outline-primary" for="checks-9c039f94488a48603a0bb776c6a1cea4-f97c5d29941bfb1b2fdab0874906ab82" dusk="choose-1">one</label> <input type="checkbox" class="btn-check" name="choose[]" id="checks-9c039f94488a48603a0bb776c6a1cea4-b8a9f715dbb64fd5c56e7783c6820a61" autocomplete="off" value="2"> <label class="btn btn-outline-primary" for="checks-9c039f94488a48603a0bb776c6a1cea4-b8a9f715dbb64fd5c56e7783c6820a61" dusk="choose-2">two</label> <input type="checkbox" class="btn-check" name="choose[]" id="checks-9c039f94488a48603a0bb776c6a1cea4-35d6d33467aae9a2e3dccb4b6b027878" autocomplete="off" value="3"> <label class="btn btn-outline-primary" for="checks-9c039f94488a48603a0bb776c6a1cea4-35d6d33467aae9a2e3dccb4b6b027878" dusk="choose-3">three</label>
    </div>
  </div>
</div>
To avoid to fill the templates with ugly strings such as :label="__('Translatable string')" it is possible to delegate translations to a dedicated reviewCallback (perhaps set as default for one or more native components).
In this example the native Laravel localization system is used, but of course you can adopt your preferred one.
This is a short help

Configuration

[
    'customs' => [
        'translatable' => [
            'extends' => 'text',
            'params' => [
                'reviewCallback' => function($component, $params) {
                    $translatable = ['label', 'help', 'pophelp'];

                    foreach($translatable as $trans) {
                        if (isset($params[$trans]) && !empty($params[$trans])) {
                            $params[$trans] = __($params[$trans]);

                            /*
                                Do not forget that certain attributes belong to the x-larastrap::field
                                wrapping the input component, and have also to be modified
                            */
                            if (isset($params['field_params'][$trans])) {
                                $params['field_params'][$trans] = __($params[$trans]);
                            }
                        }
                    }

                    return $params;
                }
            ],
        ],
    ]
]

Code

<!--

In resources/lang/en/example.php file:

return [
    'label' => 'This is a label',
    'help' => 'This is a short help',
    'pophelp' => 'This is a pophelp example text',
];

-->

<x-larastrap::translatable label="example.label" help="example.help" pophelp="example.pophelp" />
<div class="row mb-3">
  <label for="input-25d68b410520e2393d93f990ce154397" class="col-2 col-form-label">This is a label <span class="badge rounded-pill bg-primary" data-bs-toggle="popover" data-bs-trigger="hover" data-bs-content="This is a pophelp example text">?</span></label>
  <div class="col-10">
    <input id="input-25d68b410520e2393d93f990ce154397" type="text" class="form-control" name="" value="">
    <div class="form-text">
      This is a short help
    </div>
  </div>
</div>
Mobile text input tends to capitalize all texts, including those that are not intended to be capitalized, such as usernames. This custom widget adds all the required HTML attributes to disable unwanted modifications.

Configuration

[
    'customs' => [
        'username' => [
            'extends' => 'text',
            'params' => [
                'attributes' => [
                    'autocomplete' => 'off',
                    'autocorrect' => 'off',
                    'autocapitalize' => 'none',
                    'spellcheck' => 'false',
                ]
            ],
        ],
    ]
]

Code

<x-larastrap::username label="Username" name="username" />
<div class="row mb-3">
  <label for="input-42fd05eb565c1a5946e1bbdbe05ffbc1" class="col-2 col-form-label">Username</label>
  <div class="col-10">
    <input id="input-42fd05eb565c1a5946e1bbdbe05ffbc1" type="text" class="form-control" name="username" value="" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false">
  </div>
</div>