diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index dcc0949..470d98e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: matrix: php: [8.0, 7.4] laravel: [8.*, 7.*] - framework: [tailwind, tailwind-2, bootstrap-4] + framework: [tailwind, tailwind-2, bootstrap-4, bootstrap-5] dependency-version: [prefer-lowest, prefer-stable] include: - laravel: 8.* diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d1a75e..19038f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-form-components` will be documented in this file +## 3.0.0 - 2021-09-08 + +- Support for Bootstrap 5 + ## 2.5.4 - 2020-02-15 - Bugfix for old nested data diff --git a/README.md b/README.md index 02c8337..51a1a2c 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ [![Total Downloads](https://img.shields.io/packagist/dt/protonemedia/laravel-form-components.svg?style=flat-square)](https://packagist.org/packages/protonemedia/laravel-form-components) [![Buy us a tree](https://img.shields.io/badge/Treeware-%F0%9F%8C%B3-lightgreen)](https://plant.treeware.earth/protonemedia/laravel-form-components) -A set of Blade components to rapidly build forms with [Tailwind CSS v1](https://tailwindcss-custom-forms.netlify.app), [Tailwind CSS v2](https://tailwindcss-forms.vercel.app) and [Bootstrap 4](https://getbootstrap.com/docs/4.0/components/forms/). Supports validation, model binding, default values, translations, includes default vendor styling and fully customizable! +A set of Blade components to rapidly build forms with [Tailwind CSS v1](https://tailwindcss-custom-forms.netlify.app), [Tailwind CSS v2](https://tailwindcss-forms.vercel.app), [Bootstrap 4](https://getbootstrap.com/docs/4.0/components/forms/) and [Bootstrap 5 Forms](https://getbootstrap.com/docs/5.1/forms/overview/). Supports validation, model binding, default values, translations, includes default vendor styling and fully customizable! -#### ... 👀 There's a Pro version of this package in development: check out [formcomponents.pro](https://formcomponents.pro)! +Looking for Inertia/Vue.js support? Check out [formcomponents.pro](https://formcomponents.pro)! ### 📺 Want to see this package in action? Join the live stream on November 19 at 14:00 CET: [https://youtu.be/7eNZS4U7xyM](https://youtu.be/7eNZS4U7xyM) @@ -17,7 +17,8 @@ A set of Blade components to rapidly build forms with [Tailwind CSS v1](https:// * Components for input, textarea, select, multi-select, checkbox and radio elements. * Support for Tailwind v1 with [Tailwind CSS Custom Forms](https://tailwindcss-custom-forms.netlify.app). * Support for Tailwind v2 with [Tailwind Forms](https://tailwindcss-forms.vercel.app/). -* Support for [Bootstrap 4 Forms](https://getbootstrap.com/docs/4.0/components/forms/). +* Support for [Bootstrap 4 Forms](https://getbootstrap.com/docs/4.6/components/forms/). +* Support for [Bootstrap 5 Forms](https://getbootstrap.com/docs/5.1/forms/overview/). * Component logic independent from Blade views, the Tailwind and Bootstrap views use the same logic. * Bind a target to a form (or a set of elements) to provide default values (model binding). * Support for [Laravel Livewire](https://laravel-livewire.com) v2. @@ -75,7 +76,7 @@ If you're using Tailwind, make sure the right plugin ([v1](https://github.com/ta ``` - +Quick example form ## Preface @@ -130,7 +131,7 @@ You can also choose to use a `placeholder` instead of a label, and of course you ``` -By default every element shows validation errors but you can hide them if you want. +By default, every element shows validation errors, but you can hide them if you want. ```blade @@ -406,7 +407,7 @@ By the default, the errors messages are positioned under the element. To show th ### Submit button -The label defaults to *Submit* but you can use the slot to provide your own content. +The label defaults to *Submit*, but you can use the slot to provide your own content. ```blade @@ -414,21 +415,23 @@ The label defaults to *Submit* but you can use the slot to provide your own cont ``` -### Bootstrap 4 +### Bootstrap -You can switch to [Bootstrap 4](https://getbootstrap.com/docs/4.0/components/forms/) by updating the `framework` setting in the `form-components.php` configuration file. +You can switch to [Bootstrap 4](https://getbootstrap.com/docs/4.0/components/forms/) or [Bootstrap 5](https://getbootstrap.com/docs/5.0/forms/overview/) by updating the `framework` setting in the `form-components.php` configuration file. ```php return [ - 'framework' => 'bootstrap-4', + 'framework' => 'bootstrap-5', ]; ``` -There is a little bit of styling added to the `form.blade.php` view to add support for inline form groups. If you want to change it or remove it, [publish the assets](#customize-the-blade-views) and update the view file. +There is a little of styling added to the `form.blade.php` view to add support for inline form groups. If you want to change it or remove it, [publish the assets](#customize-the-blade-views) and update the view file. + +Bootstrap 5 changes a lot regarding forms. If you migrate from 4 to 5, make sure to read the migration logs about [forms](https://getbootstrap.com/docs/5.0/migration/#forms). -#### Input prepend and append +#### Input group / prepend and append -In addition to the Tailwind features, there is also support for [input groups](https://getbootstrap.com/docs/4.1/components/forms/#auto-sizing). Use the `prepend` and `append` slots to provide the contents. +In addition to the Tailwind features, with Bootstrap 4, there is also support for [input groups](https://getbootstrap.com/docs/4.6/components/forms/). Use the `prepend` and `append` slots to provide the contents. ```blade @@ -444,9 +447,28 @@ In addition to the Tailwind features, there is also support for [input groups](h ``` +With Bootstrap 5, the [input groups](https://getbootstrap.com/docs/5.0/forms/input-group/) have been simplified. You can add as many items as you would like in any order you would like. Use the `form-input-group-text` component to add text or [checkboxes](https://getbootstrap.com/docs/5.0/forms/input-group/#checkboxes-and-radios). + +```blade + + + @ + + + +``` + +#### Floating labels + +As of Bootstrap 5, you can add [floating labels](https://getbootstrap.com/docs/5.0/forms/floating-labels/) by adding the `floating` attribute to inputs, selects (excluding `multiple`), and textareas. + +```blade + +``` + #### Help text -You can add [block-level help text](https://getbootstrap.com/docs/4.1/components/forms/#help-text) to any element by using the `help` slot. +You can add [block-level help text](https://getbootstrap.com/docs/4.6/components/forms/#help-text) to any element by using the `help` slot. ```blade diff --git a/composer.json b/composer.json index 339d640..7212b9b 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,9 @@ "tailwindcss", "bootstrap", "bootstrap4", + "bootstrap5", + "bootstrap-4", + "bootstrap-5", "laravel", "form", "forms", diff --git a/config/config.php b/config/config.php index e1992e3..653e40d 100644 --- a/config/config.php +++ b/config/config.php @@ -5,7 +5,7 @@ return [ 'prefix' => '', - /** tailwind | tailwind-2 | bootstrap-4 */ + /** tailwind | tailwind-2 | bootstrap-4 | bootstrap-5 */ 'framework' => 'tailwind', 'components' => [ @@ -34,6 +34,16 @@ 'class' => Components\FormInput::class, ], + 'form-input-group' => [ + 'view' => 'form-components::{framework}.form-input-group', + 'class' => Components\FormInputGroup::class, + ], + + 'form-input-group-text' => [ + 'view' => 'form-components::{framework}.form-input-group-text', + 'class' => Components\FormInputGroupText::class, + ], + 'form-label' => [ 'view' => 'form-components::{framework}.form-label', 'class' => Components\FormLabel::class, @@ -44,6 +54,11 @@ 'class' => Components\FormRadio::class, ], + 'form-range' => [ + 'view' => 'form-components::{framework}.form-range', + 'class' => Components\FormRange::class, + ], + 'form-select' => [ 'view' => 'form-components::{framework}.form-select', 'class' => Components\FormSelect::class, diff --git a/resources/views/bootstrap-4/form-range.blade.php b/resources/views/bootstrap-4/form-range.blade.php new file mode 100644 index 0000000..5bc0747 --- /dev/null +++ b/resources/views/bootstrap-4/form-range.blade.php @@ -0,0 +1,26 @@ +
+ + + merge(['class' => 'form-control-range' . ($hasError($name) ? ' is-invalid' : '')]) !!} + + type="range" + + @if($isWired()) + wire:model{!! $wireModifier() !!}="{{ $name }}" + @else + name="{{ $name }}" + value="{{ $value }}" + @endif + + @if($label && !$attributes->get('id')) + id="{{ $id() }}" + @endif + /> + + {!! $help ?? null !!} + + @if($hasErrorAndShow($name)) + + @endif +
diff --git a/resources/views/bootstrap-5/form-checkbox.blade.php b/resources/views/bootstrap-5/form-checkbox.blade.php new file mode 100644 index 0000000..ab1cf17 --- /dev/null +++ b/resources/views/bootstrap-5/form-checkbox.blade.php @@ -0,0 +1,31 @@ +
+ merge(['class' => 'form-check-input' . ($hasError($name) ? ' is-invalid' : '')]) !!} + + type="checkbox" + + value="{{ $value }}" + + @if($isWired()) + wire:model{!! $wireModifier() !!}="{{ $name }}" + @else + name="{{ $name }}" + @endif + + @if($label && !$attributes->get('id')) + id="{{ $id() }}" + @endif + + @if($checked) + checked="checked" + @endif + /> + + + + {!! $help ?? null !!} + + @if($hasErrorAndShow($name)) + + @endif +
diff --git a/resources/views/bootstrap-5/form-errors.blade.php b/resources/views/bootstrap-5/form-errors.blade.php new file mode 100644 index 0000000..59c618d --- /dev/null +++ b/resources/views/bootstrap-5/form-errors.blade.php @@ -0,0 +1,5 @@ +@error($name, $bag) +
merge(['class' => 'invalid-feedback']) !!}> + {{ $message }} +
+@enderror \ No newline at end of file diff --git a/resources/views/bootstrap-5/form-group.blade.php b/resources/views/bootstrap-5/form-group.blade.php new file mode 100644 index 0000000..3719afd --- /dev/null +++ b/resources/views/bootstrap-5/form-group.blade.php @@ -0,0 +1,13 @@ +
merge(['class' => ($hasError($name) ? 'is-invalid' : '')]) !!}> + + +
+ {!! $slot !!} +
+ + {!! $help ?? null !!} + + @if($hasErrorAndShow($name)) + + @endif +
diff --git a/resources/views/bootstrap-5/form-input-group-text.blade.php b/resources/views/bootstrap-5/form-input-group-text.blade.php new file mode 100644 index 0000000..58f67e4 --- /dev/null +++ b/resources/views/bootstrap-5/form-input-group-text.blade.php @@ -0,0 +1 @@ +merge(['class' => 'input-group-text']) !!}>{!! $slot !!} diff --git a/resources/views/bootstrap-5/form-input-group.blade.php b/resources/views/bootstrap-5/form-input-group.blade.php new file mode 100644 index 0000000..779e00a --- /dev/null +++ b/resources/views/bootstrap-5/form-input-group.blade.php @@ -0,0 +1,13 @@ +
+ + +
merge(['class' => 'input-group' . ($hasError($name) ? ' is-invalid' : '')]) !!}> + {!! $slot !!} +
+ + {!! $help ?? null !!} + + @if($hasErrorAndShow($name)) + + @endif +
diff --git a/resources/views/bootstrap-5/form-input.blade.php b/resources/views/bootstrap-5/form-input.blade.php new file mode 100644 index 0000000..552c9d3 --- /dev/null +++ b/resources/views/bootstrap-5/form-input.blade.php @@ -0,0 +1,42 @@ +@if($type === 'hidden')
@endif +@if($floating)
@endif + + @if(!$floating) + + @endif + + merge(['class' => 'form-control' . ($type === 'color' ? ' form-control-color' : '') . ($hasError($name) ? ' is-invalid' : '')]) !!} + + type="{{ $type }}" + + @if($isWired()) + wire:model{!! $wireModifier() !!}="{{ $name }}" + @else + name="{{ $name }}" + value="{{ $value ?? ($type === 'color' ? '#000000' : '') }}" + @endif + + @if($label && !$attributes->get('id')) + id="{{ $id() }}" + @endif + + {{-- Placeholder is required as of writing --}} + @if($floating && !$attributes->get('placeholder')) + placeholder=" " + @endif + /> + + @if($floating) + + @endif + +@if($floating)
@endif + +{!! $help ?? null !!} + +@if($hasErrorAndShow($name)) + +@endif + +@if($type === 'hidden')
@endif diff --git a/resources/views/bootstrap-5/form-label.blade.php b/resources/views/bootstrap-5/form-label.blade.php new file mode 100644 index 0000000..8536ce7 --- /dev/null +++ b/resources/views/bootstrap-5/form-label.blade.php @@ -0,0 +1,3 @@ +@if($label) + +@endif \ No newline at end of file diff --git a/resources/views/bootstrap-5/form-radio.blade.php b/resources/views/bootstrap-5/form-radio.blade.php new file mode 100644 index 0000000..8840dd4 --- /dev/null +++ b/resources/views/bootstrap-5/form-radio.blade.php @@ -0,0 +1,31 @@ +
+ merge(['class' => 'form-check-input' . ($hasError($name) ? ' is-invalid' : '')]) !!} + + type="radio" + + value="{{ $value }}" + + @if($isWired()) + wire:model{!! $wireModifier() !!}="{{ $name }}" + @else + name="{{ $name }}" + @endif + + @if($label && !$attributes->get('id')) + id="{{ $id() }}" + @endif + + @if($checked) + checked="checked" + @endif + /> + + + + {!! $help ?? null !!} + + @if($hasErrorAndShow($name)) + + @endif +
diff --git a/resources/views/bootstrap-5/form-range.blade.php b/resources/views/bootstrap-5/form-range.blade.php new file mode 100644 index 0000000..08d3df9 --- /dev/null +++ b/resources/views/bootstrap-5/form-range.blade.php @@ -0,0 +1,24 @@ + + +merge(['class' => 'form-range' . ($hasError($name) ? ' is-invalid' : '')]) !!} + + type="range" + + @if($isWired()) + wire:model{!! $wireModifier() !!}="{{ $name }}" + @else + name="{{ $name }}" + value="{{ $value }}" + @endif + + @if($label && !$attributes->get('id')) + id="{{ $id() }}" + @endif +/> + +{!! $help ?? null !!} + +@if($hasErrorAndShow($name)) + +@endif diff --git a/resources/views/bootstrap-5/form-select.blade.php b/resources/views/bootstrap-5/form-select.blade.php new file mode 100644 index 0000000..f9d1248 --- /dev/null +++ b/resources/views/bootstrap-5/form-select.blade.php @@ -0,0 +1,43 @@ +@if($floating)
@endif + + @if(!$floating) + + @endif + + + + @if($floating) + + @endif + +@if($floating)
@endif + +{!! $help ?? null !!} + +@if($hasErrorAndShow($name)) + +@endif diff --git a/resources/views/bootstrap-5/form-submit.blade.php b/resources/views/bootstrap-5/form-submit.blade.php new file mode 100644 index 0000000..9dd740c --- /dev/null +++ b/resources/views/bootstrap-5/form-submit.blade.php @@ -0,0 +1,8 @@ + diff --git a/resources/views/bootstrap-5/form-textarea.blade.php b/resources/views/bootstrap-5/form-textarea.blade.php new file mode 100644 index 0000000..36024f4 --- /dev/null +++ b/resources/views/bootstrap-5/form-textarea.blade.php @@ -0,0 +1,36 @@ +@if($floating)
@endif + + @if(!$floating) + + @endif + + + + @if($floating) + + @endif + +@if($floating)
@endif + +{!! $help ?? null !!} + +@if($hasErrorAndShow($name)) + +@endif diff --git a/resources/views/bootstrap-5/form.blade.php b/resources/views/bootstrap-5/form.blade.php new file mode 100644 index 0000000..bd4ecde --- /dev/null +++ b/resources/views/bootstrap-5/form.blade.php @@ -0,0 +1,20 @@ +
merge(['class' => $hasError() ? 'needs-validation' : '']) !!} +> + + +@unless(in_array($method, ['HEAD', 'GET', 'OPTIONS'])) + @csrf +@endunless + +@if($spoofMethod) + @method($method) +@endif + + {!! $slot !!} +
diff --git a/src/Components/FormInput.php b/src/Components/FormInput.php index 6d832ae..b88bad0 100644 --- a/src/Components/FormInput.php +++ b/src/Components/FormInput.php @@ -10,6 +10,7 @@ class FormInput extends Component public string $name; public string $label; public string $type; + public bool $floating; public $value; @@ -25,12 +26,14 @@ public function __construct( $bind = null, $default = null, $language = null, - bool $showErrors = true + bool $showErrors = true, + bool $floating = false ) { $this->name = $name; $this->label = $label; $this->type = $type; $this->showErrors = $showErrors; + $this->floating = $floating && $type !== 'hidden'; if ($language) { $this->name = "{$name}[{$language}]"; diff --git a/src/Components/FormInputGroup.php b/src/Components/FormInputGroup.php new file mode 100644 index 0000000..7a94063 --- /dev/null +++ b/src/Components/FormInputGroup.php @@ -0,0 +1,23 @@ +name = $name; + $this->label = $label; + $this->showErrors = $name && $showErrors; + } +} diff --git a/src/Components/FormInputGroupText.php b/src/Components/FormInputGroupText.php new file mode 100644 index 0000000..50f767a --- /dev/null +++ b/src/Components/FormInputGroupText.php @@ -0,0 +1,18 @@ +text = $text; + } +} diff --git a/src/Components/FormRange.php b/src/Components/FormRange.php new file mode 100644 index 0000000..4a644ed --- /dev/null +++ b/src/Components/FormRange.php @@ -0,0 +1,38 @@ +name = $name; + $this->label = $label; + $this->showErrors = $showErrors; + + if ($language) { + $this->name = "{$name}[{$language}]"; + } + + $this->setValue($name, $bind, $default, $language); + } +} diff --git a/src/Components/FormSelect.php b/src/Components/FormSelect.php index df2dccb..b76280a 100644 --- a/src/Components/FormSelect.php +++ b/src/Components/FormSelect.php @@ -16,6 +16,7 @@ class FormSelect extends Component public $options; public $selectedKey; public bool $multiple; + public bool $floating; /** * Create a new component instance. @@ -30,7 +31,8 @@ public function __construct( $default = null, bool $multiple = false, bool $showErrors = true, - bool $manyRelation = false + bool $manyRelation = false, + bool $floating = false ) { $this->name = $name; $this->label = $label; @@ -51,6 +53,7 @@ public function __construct( $this->multiple = $multiple; $this->showErrors = $showErrors; + $this->floating = $floating && !$multiple; } public function isSelected($key): bool diff --git a/tests/Feature/Bootstrap4Test.php b/tests/Feature/Bootstrap4Test.php new file mode 100644 index 0000000..cc45f5f --- /dev/null +++ b/tests/Feature/Bootstrap4Test.php @@ -0,0 +1,35 @@ +markTestSkipped('Other framework configured'); + } + } + + /** @test */ + public function it_can_append_to_an_input() + { + $this->registerTestRoute('bootstrap-append'); + + $this->visit('/bootstrap-append') + ->seeInElement('.input-group-text', '.protone.media'); + } + + /** @test */ + public function it_can_prepend_to_an_input() + { + $this->registerTestRoute('bootstrap-prepend'); + + $this->visit('/bootstrap-prepend') + ->seeInElement('.input-group-text', 'info@'); + } +} diff --git a/tests/Feature/Bootstrap5Test.php b/tests/Feature/Bootstrap5Test.php new file mode 100644 index 0000000..4385a81 --- /dev/null +++ b/tests/Feature/Bootstrap5Test.php @@ -0,0 +1,53 @@ +markTestSkipped('Other framework configured'); + } + } + + /** @test */ + public function it_can_group_elements() + { + $this->registerTestRoute('bootstrap-input-group'); + + $this->visit('/bootstrap-input-group') + ->within('.input-group', function() { + return $this->seeElementCount('.form-control', 2) + ->seeElementCount('.input-group-text', 1) + ->seeInElement('.input-group-text', '@'); + }); + } + + /** @test */ + public function it_can_float_labels() + { + $this->registerTestRoute('bootstrap-floating-label'); + + $this->visit('/bootstrap-floating-label') + ->seeElementCount('label', 2) + ->seeInElement('label', 'Your Name') + ->seeElement('#name1', ['placeholder' => ' ']) + ->seeElement('#name2', ['placeholder' => 'John Doe']); + } + + /** @test */ + public function it_can_add_custom_input_classes() + { + $this->registerTestRoute('bootstrap-custom-input'); + + $this->visit('/bootstrap-custom-input') + ->seeElement('.form-control-color', ['value' => '#000000']) + ->seeElementCount('.form-switch', 1) + ->seeElement('.form-range', ['type' => 'range']); + } +} diff --git a/tests/Feature/BootstrapTest.php b/tests/Feature/BootstrapTest.php index a108787..4d56fdd 100644 --- a/tests/Feature/BootstrapTest.php +++ b/tests/Feature/BootstrapTest.php @@ -10,29 +10,11 @@ public function setUp(): void { parent::setUp(); - if (config('form-components.framework') !== 'bootstrap-4') { + if (!in_array(config('form-components.framework'), ['bootstrap-4', 'bootstrap-5'])) { $this->markTestSkipped('Other framework configured'); } } - /** @test */ - public function it_can_append_to_an_input() - { - $this->registerTestRoute('bootstrap-append'); - - $this->visit('/bootstrap-append') - ->seeInElement('.input-group-text', '.protone.media'); - } - - /** @test */ - public function it_can_prepend_to_an_input() - { - $this->registerTestRoute('bootstrap-prepend'); - - $this->visit('/bootstrap-prepend') - ->seeInElement('.input-group-text', 'info@'); - } - /** @test */ public function it_can_add_help_text() { diff --git a/tests/Feature/views/bootstrap-custom-input.blade.php b/tests/Feature/views/bootstrap-custom-input.blade.php new file mode 100644 index 0000000..726f349 --- /dev/null +++ b/tests/Feature/views/bootstrap-custom-input.blade.php @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/Feature/views/bootstrap-floating-label.blade.php b/tests/Feature/views/bootstrap-floating-label.blade.php new file mode 100644 index 0000000..1a88095 --- /dev/null +++ b/tests/Feature/views/bootstrap-floating-label.blade.php @@ -0,0 +1,4 @@ + + + + diff --git a/tests/Feature/views/bootstrap-input-group.blade.php b/tests/Feature/views/bootstrap-input-group.blade.php new file mode 100644 index 0000000..adeb551 --- /dev/null +++ b/tests/Feature/views/bootstrap-input-group.blade.php @@ -0,0 +1,7 @@ + + + + @ + + +