Skip to content

Commit

Permalink
Collapse improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerych1984 committed Sep 28, 2023
1 parent e74b93e commit 5c20ba0
Show file tree
Hide file tree
Showing 11 changed files with 797 additions and 412 deletions.
81 changes: 81 additions & 0 deletions src/AbstractToggleWidget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Yii\Bootstrap5;

use Stringable;
use Yiisoft\Arrays\ArrayHelper;
use Yiisoft\Html\Html;
use Yiisoft\Html\Tag\Base\Tag;

abstract class AbstractToggleWidget extends Widget
{
protected array $toggleOptions = [];
protected string|Stringable $toggleLabel = '';
protected bool $renderToggle = true;

abstract protected function bsToggle(): string;

public function withToggleOptions(array $options): static
{
$new = clone $this;
$new->toggleOptions = $options;

return $new;
}

public function withToggleLabel(string|Stringable $label): static
{
$new = clone $this;
$new->toggleLabel = $label;

return $new;
}

public function withToggle(bool $value): static
{
if ($this->renderToggle === $value) {
return $this;
}

$new = clone $this;
$new->renderToggle = $value;

return $new;
}

protected function prepareToggleOptions(): array
{
$options = $this->toggleOptions;
$tagName = ArrayHelper::remove($options, 'tag', 'button');
$encode = ArrayHelper::remove($options, 'encode', true);
$options['data-bs-toggle'] = $this->bsToggle();

if (!isset($options['aria-controls']) && !isset($options['aria']['controls'])) {
$options['aria-controls'] = $this->getId();
}

if ($tagName !== 'button') {
$options['role'] = 'button';
} elseif (!isset($options['type'])) {
$options['type'] = 'button';
}

if ($tagName === 'a' && !isset($options['href'])) {
$options['href'] = '#' . $this->getId();
} elseif (!isset($options['data-bs-target']) && !isset($options['data']['bs-target'])) {
$options['data-bs-target'] = '#' . $this->getId();
}

return [$tagName, $options, $encode];
}

public function renderToggle(): Tag
{
list($tagName, $options, $encode) = $this->prepareToggleOptions();

return Html::tag($tagName, $this->toggleLabel, $options)
->encode($encode);
}
}
144 changes: 55 additions & 89 deletions src/Accordion.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@

use JsonException;
use RuntimeException;
use Stringable;
use Yiisoft\Arrays\ArrayHelper;
use Yiisoft\Html\Html;

use function array_key_exists;
use function array_merge;
use function implode;
use function is_array;
use function is_numeric;
Expand Down Expand Up @@ -85,8 +84,9 @@ final class Accordion extends Widget
private bool $encodeTags = false;
private bool $autoCloseItems = true;
private array $headerOptions = [];
private array $itemToggleOptions = [];
private array $toggleOptions = [];
private array $contentOptions = [];
private array $bodyOptions = [];
private array $options = [];
private bool $flush = false;

Expand All @@ -95,29 +95,25 @@ public function getId(?string $suffix = '-accordion'): ?string
return $this->options['id'] ?? parent::getId($suffix);
}

private function getCollapseId(array $item, int $index): string
{
return ArrayHelper::getValueByPath($item, ['contentOptions', 'id'], $this->getId() . '-collapse' . $index);
}

private function getHeaderId(array $item, int $index): string
{
return ArrayHelper::getValueByPath($item, ['headerOptions', 'id'], $this->getCollapseId($item, $index) . '-heading');
}

/**
* @return string
* @throws JsonException
*/
public function render(): string
{
Html::addCssClass($this->options, ['widget' => 'accordion']);
$options = $this->options;
$options['id'] = $this->getId();
Html::addCssClass($options, ['widget' => 'accordion']);

if ($this->flush) {
Html::addCssClass($this->options, ['flush' => 'accordion-flush']);
Html::addCssClass($options, ['flush' => 'accordion-flush']);
}

if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId();
if ($this->theme) {
$options['data-bs-theme'] = $this->theme;

Check warning on line 113 in src/Accordion.php

View check run for this annotation

Codecov / codecov/patch

src/Accordion.php#L113

Added line #L113 was not covered by tests
}

return Html::div($this->renderItems(), $this->options)
return Html::div($this->renderItems(), $options)
->encode($this->encodeTags)
->render();
}
Expand Down Expand Up @@ -228,10 +224,10 @@ public function headerOptions(array $options): self
* ]
* ```
*/
public function itemToggleOptions(array $value): self
public function toggleOptions(array $options): self
{
$new = clone $this;
$new->itemToggleOptions = $value;
$new->toggleOptions = $options;

return $new;
}
Expand Down Expand Up @@ -260,6 +256,14 @@ public function options(array $value): self
return $new;
}

public function bodyOptions(array $options): self

Check warning on line 259 in src/Accordion.php

View check run for this annotation

Codecov / codecov/patch

src/Accordion.php#L259

Added line #L259 was not covered by tests
{
$new = clone $this;
$new->bodyOptions = $options;

Check warning on line 262 in src/Accordion.php

View check run for this annotation

Codecov / codecov/patch

src/Accordion.php#L261-L262

Added lines #L261 - L262 were not covered by tests

return $new;

Check warning on line 264 in src/Accordion.php

View check run for this annotation

Codecov / codecov/patch

src/Accordion.php#L264

Added line #L264 was not covered by tests
}

/**
* Remove the default background-color, some borders, and some rounded corners to render accordions
* edge-to-edge with their parent container.
Expand All @@ -286,7 +290,7 @@ private function renderItems(): string
$items = [];
$index = 0;
$expanded = in_array(true, $this->expands, true);
$allClose = !$expanded && count($this->items) === count(array_filter($this->expands, fn ($expand) => $expand === false));
$allClose = !$expanded && count($this->items) === count(array_filter($this->expands, static fn ($expand) => $expand === false));

foreach ($this->items as $item) {
if (!is_array($item)) {
Expand All @@ -302,13 +306,15 @@ private function renderItems(): string
}

$options = ArrayHelper::getValue($item, 'options', []);
$item = $this->renderItem($item, $index++);
$item = $this->renderItem($item);

Html::addCssClass($options, ['panel' => 'accordion-item']);

$items[] = Html::div($item, $options)
->encode(false)
->render();

$index++;
}

return implode('', $items);
Expand All @@ -324,104 +330,66 @@ private function renderItems(): string
*
* @return string the rendering result
*/
private function renderItem(array $item, int $index): string
private function renderItem(array $item): string
{
if (!array_key_exists('content', $item)) {
throw new RuntimeException('The "content" option is required.');
}

$header = $this->renderHeader($item, $index);
$collapse = $this->renderCollapse($item, $index);
$collapse = $this->renderCollapse($item);
$header = $this->renderHeader($collapse, ArrayHelper::getValue($item, 'headerOptions'));

return $header . $collapse;
return $header . $collapse->render();
}

/**
* Render collapse header
*/
private function renderHeader(array $item, int $index): string
private function renderHeader(Collapse $collapse, ?array $headerOptions): string
{
$options = ArrayHelper::getValue($item, 'headerOptions', $this->headerOptions);
$options = $headerOptions ?? $this->headerOptions;
$tag = ArrayHelper::remove($options, 'tag', 'h2');
$options['id'] = $this->getHeaderId($item, $index);
$toggle = $this->renderToggle($item, $index);

Html::addCssClass($options, ['widget' => 'accordion-header']);

return Html::tag($tag, $toggle, $options)
return Html::tag($tag, $collapse->renderToggle(), $options)
->encode(false)
->render();
}

/**
* Render collapse switcher
*/
private function renderToggle(array $item, int $index): string
{
$label = $item['label'];
$expand = $item['expand'] ?? false;
$collapseId = $this->getCollapseId($item, $index);

$options = array_merge(
[
'data-bs-toggle' => 'collapse',
'aria-expanded' => $expand ? 'true' : 'false',
'aria-controls' => $collapseId,
],
$item['toggleOptions'] ?? $this->itemToggleOptions
);
$tag = ArrayHelper::remove($options, 'tag', 'button');
$encode = ArrayHelper::remove($options, 'encode', $this->encodeLabels);

Html::addCssClass($options, ['accordion-button']);

if (!$expand) {
Html::addCssClass($options, ['collapsed']);
}

if ($tag === 'a') {
$options['href'] = '#' . $collapseId;
} else {
$options['data-bs-target'] = '#' . $collapseId;

if ($tag === 'button' && !isset($options['type'])) {
$options['type'] = 'button';
}
}

return Html::tag($tag, $label, $options)
->encode($encode)
->render();
}

/**
* Render collapse item
*/
private function renderCollapse(array $item, int $index): string
private function renderCollapse(array $item): Collapse
{
$expand = $item['expand'] ?? false;
$options = $item['contentOptions'] ?? $this->contentOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div');
$body = $this->renderBody($item);
$options['id'] = $this->getCollapseId($item, $index);
$toggleOptions = $item['toggleOptions'] ?? $this->toggleOptions;
$bodyOptions = $item['bodyOptions'] ?? $this->bodyOptions;

Html::addCssClass($options, ['accordion-collapse collapse']);
$toggleOptions['encode'] = $toggleOptions['encode'] ?? $this->encodeLabels;
$bodyOptions['encode'] = $bodyOptions['encode'] ?? $this->encodeTags;

if ($expand) {
Html::addCssClass($options, ['show']);
}
Html::addCssClass($options, ['accordion-collapse']);
Html::addCssClass($toggleOptions, ['accordion-button']);
Html::addCssClass($bodyOptions, ['widget' => 'accordion-body']);

if (!isset($options['aria-label'], $options['aria-labelledby'])) {
$options['aria-labelledby'] = $this->getHeaderId($item, $index);
if (!$expand) {
Html::addCssClass($toggleOptions, ['collapsed']);
}

if ($this->autoCloseItems) {
$options['data-bs-parent'] = '#' . $this->getId();
}

return Html::tag($tag, $body, $options)
->encode(false)
->render();
return Collapse::widget()
->withToggleLabel($item['label'])
->withToggleOptions($toggleOptions)
->withOptions($options)
->withContent($this->renderBody($item))
->withBodyOptions($bodyOptions)
->withCollapsed($expand)
->withToggle(false);
}

/**
Expand All @@ -445,13 +413,11 @@ private function renderBody(array $item): string
$items .= $value;
}

return Html::div($items, ['class' => 'accordion-body'])
->encode($this->encodeTags)
->render();
return $items;
}

private function isStringableObject(mixed $value): bool
{
return is_object($value) && method_exists($value, '__toString');
return $value instanceof Stringable;
}
}
Loading

0 comments on commit 5c20ba0

Please sign in to comment.