Skip to content

Commit

Permalink
Add Bootstrap5 themes + minor improvements (#302)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed Dec 26, 2023
1 parent 60c4e36 commit 4cf43a2
Show file tree
Hide file tree
Showing 23 changed files with 640 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Expand Up @@ -33,6 +33,7 @@
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
/tests export-ignore
/themes-preview export-ignore
/docs export-ignore

# Avoid merge conflicts in CHANGELOG
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -21,7 +21,7 @@
"require": {
"php": "^8.1",
"yiisoft/friendly-exception": "^1.0",
"yiisoft/html": "^3.3",
"yiisoft/html": "^3.4",
"yiisoft/widget": "^2.2"
},
"require-dev": {
Expand Down
67 changes: 67 additions & 0 deletions config/theme-bootstrap5-horizontal.php
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

use Yiisoft\Form\Field\Button;
use Yiisoft\Form\Field\ButtonGroup;
use Yiisoft\Form\Field\Checkbox;
use Yiisoft\Form\Field\CheckboxList;
use Yiisoft\Form\Field\ErrorSummary;
use Yiisoft\Form\Field\RadioList;
use Yiisoft\Form\Field\Range;
use Yiisoft\Form\Field\ResetButton;
use Yiisoft\Form\Field\Select;
use Yiisoft\Form\Field\SubmitButton;

return [
'template' => "{label}\n<div class=\"col-sm-10\">{input}\n{hint}\n{error}</div>",
'containerClass' => 'mb-3 row',
'labelClass' => 'col-sm-2 col-form-label',
'inputClass' => 'form-control',
'hintClass' => 'form-text',
'errorClass' => 'invalid-feedback',
'inputValidClass' => 'is-valid',
'inputInvalidClass' => 'is-invalid',
'fieldConfigs' => [
Checkbox::class => [
'inputContainerTag()' => ['div'],
'addInputContainerClass()' => ['form-check'],
'inputClass()' => ['form-check-input'],
'inputLabelClass()' => ['form-check-label'],
],
CheckboxList::class => [
'addCheckboxAttributes()' => [['class' => 'form-check-input']],
'addCheckboxLabelAttributes()' => [['class' => 'form-check']],
],
RadioList::class => [
'addRadioAttributes()' => [['class' => 'form-check-input']],
'addRadioLabelAttributes()' => [['class' => 'form-check']],
],
ErrorSummary::class => [
'containerClass()' => ['alert alert-danger'],
'listAttributes()' => [['class' => 'mb-0']],
'headerTag()' => ['h4'],
'headerAttributes()' => [['class' => 'alert-heading']],
],
Button::class => [
'buttonClass()' => ['btn btn-secondary'],
],
SubmitButton::class => [
'buttonClass()' => ['btn btn-primary'],
],
ResetButton::class => [
'buttonClass()' => ['btn btn-secondary'],
],
ButtonGroup::class => [
'inputContainerTag()' => ['div'],
'addInputContainerClass()' => ['btn-group'],
'addButtonAttributes()' => [['class' => 'btn btn-secondary']],
],
Range::class => [
'inputClass()' => ['form-range'],
],
Select::class => [
'inputClass()' => ['form-select'],
],
],
];
67 changes: 67 additions & 0 deletions config/theme-bootstrap5-vertical.php
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

use Yiisoft\Form\Field\Button;
use Yiisoft\Form\Field\ButtonGroup;
use Yiisoft\Form\Field\Checkbox;
use Yiisoft\Form\Field\CheckboxList;
use Yiisoft\Form\Field\ErrorSummary;
use Yiisoft\Form\Field\RadioList;
use Yiisoft\Form\Field\Range;
use Yiisoft\Form\Field\ResetButton;
use Yiisoft\Form\Field\Select;
use Yiisoft\Form\Field\SubmitButton;

return [
'template' => "{label}\n{input}\n{hint}\n{error}",
'containerClass' => 'mb-3',
'labelClass' => 'form-label',
'inputClass' => 'form-control',
'hintClass' => 'form-text',
'errorClass' => 'invalid-feedback',
'inputValidClass' => 'is-valid',
'inputInvalidClass' => 'is-invalid',
'fieldConfigs' => [
Checkbox::class => [
'inputContainerTag()' => ['div'],
'addInputContainerClass()' => ['form-check'],
'inputClass()' => ['form-check-input'],
'inputLabelClass()' => ['form-check-label'],
],
CheckboxList::class => [
'addCheckboxAttributes()' => [['class' => 'form-check-input']],
'addCheckboxLabelAttributes()' => [['class' => 'form-check']],
],
RadioList::class => [
'addRadioAttributes()' => [['class' => 'form-check-input']],
'addRadioLabelAttributes()' => [['class' => 'form-check']],
],
ErrorSummary::class => [
'containerClass()' => ['alert alert-danger'],
'listAttributes()' => [['class' => 'mb-0']],
'headerTag()' => ['h4'],
'headerAttributes()' => [['class' => 'alert-heading']],
],
Button::class => [
'buttonClass()' => ['btn btn-secondary'],
],
SubmitButton::class => [
'buttonClass()' => ['btn btn-primary'],
],
ResetButton::class => [
'buttonClass()' => ['btn btn-secondary'],
],
ButtonGroup::class => [
'inputContainerTag()' => ['div'],
'addInputContainerClass()' => ['btn-group'],
'addButtonAttributes()' => [['class' => 'btn btn-secondary']],
],
Range::class => [
'inputClass()' => ['form-range'],
],
Select::class => [
'inputClass()' => ['form-select'],
],
],
];
14 changes: 14 additions & 0 deletions src/Field/CheckboxList.php
Expand Up @@ -49,6 +49,20 @@ public function addCheckboxAttributes(array $attributes): self
return $new;
}

public function checkboxLabelAttributes(array $attributes): self
{
$new = clone $this;
$new->widget = $this->widget->checkboxLabelAttributes($attributes);
return $new;
}

public function addCheckboxLabelAttributes(array $attributes): self
{
$new = clone $this;
$new->widget = $this->widget->addCheckboxLabelAttributes($attributes);
return $new;
}

/**
* @param array[] $attributes
*/
Expand Down
43 changes: 42 additions & 1 deletion src/Field/ErrorSummary.php
Expand Up @@ -4,8 +4,10 @@

namespace Yiisoft\Form\Field;

use InvalidArgumentException;
use Yiisoft\Form\Field\Base\BaseField;
use Yiisoft\Html\Html;
use Yiisoft\Html\Tag\CustomTag;

use function in_array;

Expand All @@ -24,8 +26,15 @@ final class ErrorSummary extends BaseField

private string $footer = '';
private array $footerAttributes = [];

/**
* @var non-empty-string|null
*/
private ?string $headerTag = 'div';
private string $header = '';
private bool $headerEncode = true;
private array $headerAttributes = [];

private array $listAttributes = [];

/**
Expand Down Expand Up @@ -111,6 +120,32 @@ public function header(string $value): self
return $new;
}

/**
* Set the header tag name.
*
* @param string|null $tag Header tag name.
*/
public function headerTag(?string $tag): self
{
if ($tag === '') {
throw new InvalidArgumentException('Tag name cannot be empty.');
}

$new = clone $this;
$new->headerTag = $tag;
return $new;
}

/**
* Whether header content should be HTML-encoded.
*/
public function headerEncode(bool $encode): self
{
$new = clone $this;
$new->headerEncode = $encode;
return $new;
}

/**
* Set header attributes for the error summary.
*
Expand Down Expand Up @@ -173,7 +208,13 @@ protected function generateContent(): ?string
$content = [];

if ($this->header !== '') {
$content[] = Html::div($this->header, $this->headerAttributes)->render();
$content[] = $this->headerTag === null
? ($this->headerEncode ? Html::encode($this->header) : $this->header)
: CustomTag::name($this->headerTag)
->attributes($this->headerAttributes)
->content($this->header)
->encode($this->headerEncode)
->render();
}

$content[] = Html::ul()
Expand Down
14 changes: 14 additions & 0 deletions src/Field/RadioList.php
Expand Up @@ -47,6 +47,20 @@ public function addRadioAttributes(array $attributes): self
return $new;
}

public function radioLabelAttributes(array $attributes): self
{
$new = clone $this;
$new->widget = $this->widget->radioLabelAttributes($attributes);
return $new;
}

public function addRadioLabelAttributes(array $attributes): self
{
$new = clone $this;
$new->widget = $this->widget->addRadioLabelAttributes($attributes);
return $new;
}

/**
* @param array[] $attributes
*/
Expand Down
10 changes: 8 additions & 2 deletions src/PureField.php
Expand Up @@ -156,9 +156,15 @@ final public static function hidden(
->inputData(new PureInputData($name, $value));
}

final public static function image(array $config = [], ?string $theme = null): Image
final public static function image(?string $url = null, array $config = [], ?string $theme = null): Image
{
return Image::widget(config: $config, theme: $theme ?? static::DEFAULT_THEME);
$field = Image::widget(config: $config, theme: $theme ?? static::DEFAULT_THEME);

if ($url !== null) {
$field = $field->src($url);
}

return $field;
}

final public static function number(
Expand Down
11 changes: 11 additions & 0 deletions src/ThemePath.php
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Form;

final class ThemePath
{
public const BOOTSTRAP5_HORIZONTAL = __DIR__ . '/../config/theme-bootstrap5-horizontal.php';
public const BOOTSTRAP5_VERTICAL = __DIR__ . '/../config/theme-bootstrap5-vertical.php';
}
18 changes: 18 additions & 0 deletions tests/ConfigTest.php
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\Form\Tests;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Yiisoft\Form\PureField;
use Yiisoft\Form\ThemeContainer;
Expand Down Expand Up @@ -56,6 +57,23 @@ public function testCustomParams(): void
);
}

public static function dataTheme(): array
{
$configDir = dirname(__DIR__) . '/config';
return [
[$configDir . '/theme-bootstrap5-horizontal.php'],
[$configDir . '/theme-bootstrap5-vertical.php'],
];
}

#[DataProvider('dataTheme')]
public function testTheme(string $theme): void
{
$result = require $theme;

$this->assertIsArray($result);
}

private function getBootstrapList(?array $params = null): array
{
if ($params === null) {
Expand Down
44 changes: 44 additions & 0 deletions tests/Field/CheckboxListTest.php
Expand Up @@ -125,6 +125,48 @@ public function testCheckboxAttributes(): void
$this->assertSame($expected, $result);
}

public function testAddCheckboxLabelAttributes(): void
{
$result = CheckboxList::widget()
->itemsFromValues(['Red', 'Blue'])
->name('CheckboxListForm[color]')
->addCheckboxLabelAttributes(['class' => 'control'])
->addCheckboxLabelAttributes(['data-key' => 'x100'])
->render();

$expected = <<<HTML
<div>
<div>
<label class="control" data-key="x100"><input type="checkbox" name="CheckboxListForm[color][]" value="Red"> Red</label>
<label class="control" data-key="x100"><input type="checkbox" name="CheckboxListForm[color][]" value="Blue"> Blue</label>
</div>
</div>
HTML;

$this->assertSame($expected, $result);
}

public function testCheckboxLabelAttributes(): void
{
$result = CheckboxList::widget()
->itemsFromValues(['Red', 'Blue'])
->name('CheckboxListForm[color]')
->checkboxLabelAttributes(['data-key' => 'x100'])
->checkboxLabelAttributes(['class' => 'control'])
->render();

$expected = <<<HTML
<div>
<div>
<label class="control"><input type="checkbox" name="CheckboxListForm[color][]" value="Red"> Red</label>
<label class="control"><input type="checkbox" name="CheckboxListForm[color][]" value="Blue"> Blue</label>
</div>
</div>
HTML;

$this->assertSame($expected, $result);
}

public function testAddIndividualInputAttributes(): void
{
$result = CheckboxList::widget()
Expand Down Expand Up @@ -430,6 +472,8 @@ public function testImmutability(): void

$this->assertNotSame($field, $field->checkboxAttributes([]));
$this->assertNotSame($field, $field->addCheckboxAttributes([]));
$this->assertNotSame($field, $field->checkboxLabelAttributes([]));
$this->assertNotSame($field, $field->addCheckboxLabelAttributes([]));
$this->assertNotSame($field, $field->individualInputAttributes([]));
$this->assertNotSame($field, $field->addIndividualInputAttributes([]));
$this->assertNotSame($field, $field->items([]));
Expand Down

0 comments on commit 4cf43a2

Please sign in to comment.