portavice/bladestrap

Blade components for Bootstrap 5

v1.4.0 2024-11-22 13:42 UTC

This package is auto-updated.

Last update: 2024-11-22 13:52:19 UTC


README

MIT Licensed PHP Tests Code style check Latest version Total downloads

Bladestrap provides Laravel Blade components for the Bootstrap 5 frontend framework.

Contents

Installation

First, install the package via Composer:

composer require portavice/bladestrap

Within a Laravel application, the package will automatically register itself.

Note

If you only use parts of the Laravel framework (such as illuminate/view), make sure to follow the instructions in the section on usage without Laravel.

Install Bootstrap

Note that you need to include the Bootstrap files on your own.

  1. If you haven't added Bootstrap as one of your dependencies, you can do so via npm:
    npm install bootstrap
  2. Add the following to your webpack.mix.js to copy the required Bootstrap files to your public directory:
    let bootstrapFiles = [
        'node_modules/bootstrap/dist/css/bootstrap.min.css',
        'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js',
    ];
    mix.copy(bootstrapFiles, 'public/lib');
  3. Include CSS and JavaScript in resources/views/layouts/app.blade.php:
    <link rel="stylesheet" href="{{ mix('lib/bootstrap.min.css') }}">
    <script src="{{ mix('lib/bootstrap.bundle.min.js') }}"></script>

You may need to adjust the steps above to your custom project configuration. If you have a custom Bootstrap build you are responsible to include the necessary parts of Bootstrap yourself.

Configure Bladestrap

Usually this should not be necessary, but if you need to overwrite the default configuration, create and edit config/bladestrap.php:

php artisan vendor:publish --tag="bladestrap-config"

Customize views

If you want to customize the views, publish them to resources\views\vendor\bladestrap\components and edit them to meet your requirements:

php artisan vendor:publish --tag="bladestrap-views"

You may want to delete the views you haven't changed to benefit from package updates automatically.

Usage

The components are placed in the bs namespace, such that they can be used via:

<x-bs::component-name> <!-- Replace component-name with one of the component names described below -->

Components can be enhanced with additional classes from Bootstrap or your own CSS.

Specifically handled attributes are documented with type annotations in the @props in the respective Blade template under resources/views/components.

Alerts

Alerts are of variant alert-info by default and can be dismissible (with a close button).

<x-bs::alert>My info alert</x-bs::alert>
<x-bs::alert variant="primary">My primary alert</x-bs::alert>
<x-bs::alert variant="secondary" :dismissible="true">My dismissible secondary alert</x-bs::alert>

Badges

Badges are of variant badge-primary default:

<x-bs::badge>My primary badge</x-bs::badge>
<x-bs::badge variant="secondary">My secondary badge</x-bs::badge>

Breadcrumb

The breadcrumb container is a <x-bs::breadcrumb> (typically placed within your layouts/app.blade.php):

@hasSection('breadcrumbs')
    <x-bs::breadcrumb container-class="mt-3" class="bg-light">
        <x-bs::breadcrumb.item href="{{ route('dashboard') }}">{{ __('Dashboard') }}</x-bs::breadcrumb.item>
        @yield('breadcrumbs')
    </x-bs::breadcrumb>
@endif

Items can be added via <x-bs::breadcrumb.item :href="route('route-name')">Title</x-bs::breadcrumb.item>.

Buttons

To create buttons or button-like links with Bootstrap's btn-* classes you can use

  • <x-bs::button> (becomes a <button>)
  • and <x-bs::button.link> (becomes an <a>). Per default btn-primary is used, you can change that with the variant.
<x-bs::button href="{{ route('my-route') }}" variant="danger">{{ __('Delete') }}</x-bs::button>
<x-bs::button.link href="{{ route('my-route') }}">{{ __('My title') }}</x-bs::button.link>

To disable a button or link, just add disabled="true" which automatically adds the corresponding class and aria-disabled="true" as recommended by the Bootstrap documentation.

Button groups

Buttons can be grouped:

<x-bs::button.group>
    <x-bs::button>Button 1</x-bs::button>
    <x-bs::button variant="secondary">Button 2</x-bs::button>
</x-bs::button.group>

Button toolbars

Button groups can be grouped into a toolbar:

<x-bs:toolbar aria-label="Toolbar with two groups">
    <x-bs::button.group aria-label="First group">
        <x-bs::button>Button 1</x-bs::button>
        <x-bs::button>Button 2</x-bs::button>
    </x-bs::button.group>
    <x-bs::button.group aria-label="Second group">
        <x-bs::button variant="secondary">Button 3</x-bs::button>
        <x-bs::button variant="secondary">Button 4</x-bs::button>
    </x-bs::button.group>
</x-bs:toolbar>

Dropdowns

Dropdown buttons can be added as follows:

<x-bs::dropdown.button direction="end" variant="secondary">
    My button
    <x-slot:dropdown>
        <x-bs::dropdown.item href="#">Item 1</x-bs::dropdown.item>
        <x-bs::dropdown.item href="#">Item 2</x-bs::dropdown.item>
    </x-slot:dropdown>
</x-bs::dropdown.button>

The direction attribute can be used to set the direction of the dropdown overlay. It defaults to down. variant (default primary) is inherited from the button component.

Within the <x-slot:dropdown> you may place headers and items:

<x-bs::dropdown.header>My header</x-bs::dropdown.header>
<x-bs::dropdown.item href="#">Item</x-bs::dropdown.item>

Note that Bootstrap's dropdowns require Popper, which needs to be included separately if you don't use Bootstrap's bootstrap.bundle.min.js.

Forms

Use <x-bs::form> to create forms (method defaults to POST), any additional attributes passed to the form component will be outputted as well:

<x-bs::form method="PUT" action="{{ route('my-route.update') }}" class="my-3">
    <!-- TODO: add form fields and buttons -->
</x-bs::form>

Bladestrap will inject an CSRF token field for all methods except GET automatically. Bladestrap will also configure method spoofing for PUT, PATCH and DELETE forms.

Types of form fields

Bladestrap has wide support for Bootstrap's form fields.

<x-bs::form.field name="my_field_name" type="text" value="My value">{{ __('My label') }}</x-bs::form.field>

Note that the content of the form field becomes the label. This allows to include icons etc. If you don't want to add a label, don't pass any content:

<x-bs::form.field name="my_field_name" type="text" value="My value"/>

All attributes will be passed to the <input>, <select>, <textarea> - except

  • the attributes which start with container- (those will be applied to the container for the label and input)
  • and the attributes which start with label- (those will be applied to the label).

The following types are supported as values for the type attribute:

  • checkbox - creates a normal checkbox, requires :options
  • color
  • date
  • datetime-local*
  • email
  • file
  • hidden - ignores slots for label, hint and input group
  • month*
  • number
  • password
  • radio - creates a radio, requires :options
  • range
  • select - creates a dropdown (<select> with <option>s), requires :options
  • switch - creates a toggle switch, requires :options
  • tel
  • text
  • textarea - creates a <textarea>
  • time*
  • url
  • week*

The types (marked with *) listed above don't have full browser support.

Options

Radio buttons, checkboxes and selects need a :options attribute providing an iterable of value/label pairs, e.g.

  • an array, as in :options="[1 => 'Label 1', 2 => 'Label 2']"
  • an Illuminate\Support\Collection, such as
    • :options="User::query()->pluck('name', 'id')"
    • or :options="User::query()->pluck('name', 'id')->prepend(__('all'), '')"
  • a Portavice\Bladestrap\Support\Options which allows to set custom attributes for each option. For checkboxes, radios and switches, custom attributes prefixed with check-container- or check-label- are applied to the .form-check or .form-check-label respectively. If labels contain HTML, set :allow-html="true".

An Portavice\Bladestrap\Support\Options can be used to easily create an iterable based on

  • an array
    use Portavice\Bladestrap\Support\Options;
    
    // Array with custom attributes
    Options::fromArray(
          [
              1 => 'One',
              2 => 'Two',
          ],
          static fn ($optionValue, $label) => [
              'data-value' => $optionValue + 2,
          ]
      );
  • an enum implementing the BackedEnum interface
    use Portavice\Bladestrap\Support\Options;
    
    // All enum cases with labels based on the value
    Options::fromEnum(MyEnum::class);
    
    // ... with labels based on the name
    Options::fromEnum(MyEnum::class, 'name');
    
    // ... with labels based on the result of the myMethod function
    Options::fromEnum(MyEnum::class, 'myMethod');
    
    // Only a subset of enum cases
    Options::fromEnum([MyEnum::Case1, MyEnum::Case2]);
  • an array or Illuminate\Database\Eloquent\Collection of Eloquent models (the primary key becomes the value, label must be defined)
    use Portavice\Bladestrap\Support\Options;
    
    // Array of models with labels based on a column or accessor
    Options::fromModels([$user1, $user2, ...$moreUsers], 'name');
    
    // Collection of models with labels based on a column or accessor
    Options::fromModels(User::query()->get(), 'name');
    
    // ... with labels based on a Closure
    Options::fromModels(
        User::query()->get(),
        static fn (User $user) => sprintf('%s (%s)', $user->name, $user->id)
    );
    
    // ... with custom attributes for <option>s using a \Closure defining an ComponentAttributeBag
    Options::fromModels(User::query()->get(), 'name', static function (User $user) {
        return (new ComponentAttributeBag([]))->class([
            'user-option',
            'inactive' => $user->isInactive(),
        ]);
    });
    
    // ... with custom attributes for <option>s using a \Closure defining an array of attributes
    Options::fromModels(User::query()->get(), 'name', fn (User $user) => [
        'data-title' => $user->title,
    ]);

Additional options can be prepended/appended to an Options:

use Portavice\Bladestrap\Support\Options;

$options = Options::fromModels(User::query()->get(), 'name')
    ->sortAlphabetically() // call sort for current options
    ->prepend('all', '') // adds an option with an empty value before first option
    ->append('label for last option', 'value') // adds an option after the last option
    ->prependMany([ // adds options before the first option (value => label)
        'value-1' => 'first prepended option',
        'value-2' => 'second prepended option',
    ]);

Radio buttons (allows to select one of multiple values):

<x-bs::form.field name="my_field_name" type="radio" :options="$options"
                  :value="$value">{{ __('My label') }}</x-bs::form.field>

Multiple checkboxes (allows to select multiple values):

<x-bs::form.field id="my_field_name" name="my_field_name[]" type="checkbox" :options="$options"
                  :value="$value">{{ __('My label') }}</x-bs::form.field>

Single checkbox (just one option):

<x-bs::form.field id="my_field_name" type="checkbox" :options="[1 => 'Option enabled']"
                  :value="$value">{{ __('My label') }}</x-bs::form.field>
<x-bs::form.field id="my_field_name" type="checkbox" :allow-html="true"
                  :options="Options::one('Option <strong>with HTML</strong> enabled')"
                  :value="$value">{{ __('My label') }}</x-bs::form.field>

Select (allows to select one of multiple values):

<x-bs::form.field name="my_field_name" type="select" :options="$options"
                  :value="$value">{{ __('My label') }}</x-bs::form.field>

Multi-Select (allows to select multiple values):

<x-bs::form.field id="my_field_name" name="my_field_name[]" type="select" multi :options="$options"
                  :value="$value">{{ __('My label') }}</x-bs::form.field>

Disabled, readonly, required

The attributes :disabled, :readonly, and :required accept a boolean value, e.g. :disabled="true" or :required="isset($var)".

Per default fields with :required="true" are marked with a * after the label. This behavior can be disabled via configuration (for all fields) or with :mark-as-required="false" (for a single field).

Input groups

To add text at the left or the right of a form field (except checkboxes and radio buttons), you can use the slots <x-slot:prependText> and <x-slot:appendText> which makes an input group:

<x-bs::form.field name="my_field_name" type="number" min="0" max="100" step="0.1">
    {{ __('My label') }}
    <x-slot:prependText></x-slot:prependText>
    <x-slot:appendText></x-slot:appendText>
</x-bs::form.field>

By default, the appended/prepended text is wrapped within a <label> class="input-group-text" associated with the field. To avoid this, set :container="false" attribute on the slot which allows to define to add buttons for example:

<x-bs::form.field name="file" type="file">
    File
    <x-slot:appendText>
        <x-bs::button.link variant="primary" href="test.pdf">Download current file</x-bs::button.link>
    </x-slot:appendText>
</x-bs::form.field>'

Alternatively, an appendText slot can include a <x-bs::form.field:nested-in-group="true">:

<x-bs::form.field name="price_from" type="number" min="1" step="1">
    <x-slot:prependText>from</x-slot:prependText>
    Price
    <x-slot:appendText :container="false">
        <x-bs::form.field name="price_until" type="number" min="1" step="1" :nested-in-group="true">
            <x-slot:prependText>until</x-slot:prependText>
            <x-slot:appendText></x-slot:appendText>
        </x-bs::form.field>
    </x-slot:appendText>
</x-bs::form.field>

Hints

<x-slot:hint> can be used to add a text with custom hints (.form-text) below the field, which will be automatically referenced via aria-describedby by the input:

<x-bs::form.field name="my_field_name" type="text">
    {{ __('My label') }}
    <x-slot:hint>Hint</x-slot:hint>
</x-bs::form.field>

Prefill values from query parameters

Setting :from-query="true" will extract values from the query parameters of the current route.

<x-bs::form.field id="name" name="filter[name]" type="text" :from-query="true">{{ __('Name') }}</x-bs::form.field>

A form with the example field above on a page /my-page?filter[name]=Test will set "Test" as the prefilled value of the field, while /my-page will have an empty value.

To pass default filters applied if no query parameters are set, use ValueHelper::setDefaults:

use Portavice\Bladestrap\Support\ValueHelper;

ValueHelper::setDefaults([
    'filter.name' => 'default',
])

Error messages

All form fields show corresponding error messages automatically if present (server-side validation). If you want to show them independent of a form field, you can use the component directly:

<x-bs::form.feedback name="{{ $name }}"/>

Both <x-bs::form.feedback> and <x-bs::form.field> support to use another than the default error bag via the :errors attribute.

Links

Colored links can be placed via <x-bs::link>, the attributes opacity and opacityHover define opacity.

<x-bs::link href="{{ route('my-route') }}">Link text</x-bs::link>
<x-bs::link href="{{ route('my-route') }}" variant="danger">Link text</x-bs::link>
<x-bs::link href="{{ route('my-route') }}" opacity="25">Link text</x-bs::link>

List groups

<x-bs::list> is a list group, a container for multiple <x-bs::list.item>. :flush="true" enables flush behavior, :horizontal="true changes the layout from vertical to horizontal.

Items can be added via <x-bs::list.item>:

<x-bs::list>
    <x-bs::list.item>Item 1</x-bs::list.item>
    <x-bs::list.item :active="true">Item 2</x-bs::list.item>
</x-bs::list>

:active="true" highlights the active item, :disabled="true" makes it appear disabled.

Modals

Modals can be created via <x-bs::modal> with optional slots for title and footer. Both slots accept additional classes and other attributes. If you don't want a <h1> container for the title, change it via container="h2" etc.

<x-bs::modal.button modal="my-modal">Open modal</x-bs::modal.button>
<x-bs::modal id="my-modal">
    <x-slot:title>My modal title</x-slot:title>
    <x-slot:footer>
        <x-bs::button>Test</x-bs::button>
    </x-slot:footer>
</x-bs::modal>

<x-bs::modal> supports the following optional attributes:

  • centered to center the modal vertically (defaults to false)
  • fade for the fade effect when opening the modal (defaults to true)
  • fullScreen to force fullscreen (defaults to false, pass true to always enforce full screen or sm to enforce for sizes below the sm breakpoint etc.),
  • scrollable to enable a vertical scrollbar for long dialog content (defaults to false)
  • staticBackdrop' to enforce that clicking outside of it does not close the modal (defaults to false)
  • closeButton sets the variant of the close button in the modal footer (defaults to secondary, false to disable the close button),
  • closeButtonTitle for the title of the close button (defaults to "Close")

A <x-bs::modal.button modal="my-modal"> opens the modal with the ID my-modal. You may pass any additional attributes as known from <x-bs::button>.

Navigation

<x-bs::nav> creates a nav container, use container="ol" to change the container element from the default <ul> to <ol>.

Navigation items can be added via <x-bs::nav.item href="{{ route('route-name') }}">Current Page</x-bs::nav.item>.

A navigation item may open a dropdown if you enabled this by adding a dropdown slot:

<x-bs::nav.item id="navbarUserDropdown">
    Dropdown link text
    <x-slot:dropdown class="dropdown-menu-end">
        <!-- dropdown content-->
    </x-slot:dropdown>
</x-bs::list.item>

Usage without Laravel

Bladestrap uses config() and request() helpers. If you want to use Bladestrap without Laravel, you need to define the two helpers in your application, for example (may need to be adapted to the framework you use):

use Illuminate\Http\Request;
use Illuminate\Support\Arr;

$configFile = [
    'bladestrap' => require __DIR__ . '/../vendor/portavice/bladestrap/config/bladestrap.php',
];
function config(array|string|null $key, mixed $default = null): mixed
{
    global $configFile;
    return Arr::get($configFile, $key, $default);
}

$request = Request::capture();
function request(array|string|null $key = null, mixed $default = null): mixed
{
    global $request;
    return $key === null ? $request : $request->input($key, $default);
}

In addition, you have to do the registrations of the BladestrapServiceProvider yourself:

use Illuminate\View\Factory;
use Portavice\Bladestrap\Macros\ComponentAttributeBagExtension;

// Register macros as BladestrapServiceProvider would do.
ComponentAttributeBagExtension::registerMacros();

/* @var Factory $viewFactory */
// Add components in bs namespace to your views.
$viewFactory->addNamespace('bs', __DIR__ . '/../vendor/portavice/bladestrap/resources/views');