Skip to content

Commit e8ae58c

Browse files
committed
[FEATURE] Introduce directives for bootstrap cards
1 parent 5efd6ac commit e8ae58c

File tree

9 files changed

+420
-94
lines changed

9 files changed

+420
-94
lines changed

packages/guides-theme-bootstrap/resources/config/guides-theme-bootstrap.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
declare(strict_types=1);
44

5+
use phpDocumentor\Guides\Bootstrap\Directives\AccordionDirective;
6+
use phpDocumentor\Guides\Bootstrap\Directives\AccordionItemDirective;
57
use phpDocumentor\Guides\Bootstrap\Directives\CardDirective;
68
use phpDocumentor\Guides\Bootstrap\Directives\CardFooterDirective;
79
use phpDocumentor\Guides\Bootstrap\Directives\CardGridDirective;
@@ -26,6 +28,9 @@
2628
->bind('$startingRule', service(DirectiveContentRule::class))
2729
->instanceof(BaseDirective::class)
2830
->tag('phpdoc.guides.directive')
31+
->set(AccordionDirective::class)
32+
->set(AccordionItemDirective::class)
33+
->set(CardDirective::class)
2934
->set(CardDirective::class)
3035
->set(CardFooterDirective::class)
3136
->set(CardHeaderDirective::class)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<div class="accordion {%- if node.classes %} {{ node.classesString }}{% endif -%}" id="{{ node.anchor }}">
2+
{% for item in node.value %}
3+
<div class="accordion-item {%- if item.classes %} {{ item.classesString }}{% endif -%}">
4+
<h{{ item.title.level }} class="accordion-header" id="{{ item.anchor }}-heading">
5+
<button class="accordion-button {%- if not item.show %} collapsed{% endif -%}" type="button" data-bs-toggle="collapse" data-bs-target="#{{ item.anchor }}"
6+
aria-expanded="{%- if item.show %}true{% else %}false{% endif -%}" aria-controls="{{ item.anchor }}">
7+
{{ renderNode(item.title.value) }}
8+
</button>
9+
</h{{ item.title.level }}>
10+
<div id="{{ item.anchor }}" class="accordion-collapse collapse {%- if item.show %} show{% endif -%}" aria-labelledby="{{ item.anchor }}-heading"
11+
data-bs-parent="#{{ node.anchor }}">
12+
<div class="accordion-body">
13+
{{ renderNode(item.value) }}
14+
</div>
15+
</div>
16+
</div>
17+
{%- endfor %}
18+
</div>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Bootstrap\Directives;
15+
16+
use phpDocumentor\Guides\Bootstrap\Nodes\AccordionItemNode;
17+
use phpDocumentor\Guides\Bootstrap\Nodes\AccordionNode;
18+
use phpDocumentor\Guides\Nodes\CollectionNode;
19+
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
20+
use phpDocumentor\Guides\Nodes\Node;
21+
use phpDocumentor\Guides\RestructuredText\Directives\SubDirective;
22+
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
23+
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
24+
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
25+
use Psr\Log\LoggerInterface;
26+
27+
class AccordionDirective extends SubDirective
28+
{
29+
public const NAME = 'accordion';
30+
31+
public function __construct(
32+
protected Rule $startingRule,
33+
private readonly LoggerInterface $logger,
34+
) {
35+
parent::__construct($startingRule);
36+
}
37+
38+
public function getName(): string
39+
{
40+
return self::NAME;
41+
}
42+
43+
protected function processSub(
44+
BlockContext $blockContext,
45+
CollectionNode $collectionNode,
46+
Directive $directive,
47+
): Node|null {
48+
$originalChildren = $collectionNode->getChildren();
49+
$children = [];
50+
foreach ($originalChildren as $child) {
51+
if ($child instanceof AccordionItemNode) {
52+
$children[] = $child;
53+
} else {
54+
$this->logger->warning('An accordion may only accordion-items. ', $blockContext->getLoggerInformation());
55+
}
56+
}
57+
58+
$id = $directive->getOption('name')->toString();
59+
if ($id === '') {
60+
$id = 'accordion';
61+
$this->logger->warning('An accordion must have a unique name as parameter. ', $blockContext->getLoggerInformation());
62+
}
63+
64+
return new AccordionNode(
65+
$this->getName(),
66+
$directive->getData(),
67+
$directive->getDataNode() ?? new InlineCompoundNode(),
68+
$children,
69+
$id,
70+
);
71+
}
72+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Bootstrap\Directives;
15+
16+
use phpDocumentor\Guides\Bootstrap\Nodes\AccordionItemNode;
17+
use phpDocumentor\Guides\Bootstrap\Nodes\CardNode;
18+
use phpDocumentor\Guides\Nodes\CollectionNode;
19+
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
20+
use phpDocumentor\Guides\Nodes\Node;
21+
use phpDocumentor\Guides\Nodes\TitleNode;
22+
use phpDocumentor\Guides\ReferenceResolvers\AnchorNormalizer;
23+
use phpDocumentor\Guides\RestructuredText\Directives\SubDirective;
24+
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
25+
use phpDocumentor\Guides\RestructuredText\Parser\Directive;
26+
use phpDocumentor\Guides\RestructuredText\Parser\Productions\Rule;
27+
use phpDocumentor\Guides\RestructuredText\TextRoles\GenericLinkProvider;
28+
use Psr\Log\LoggerInterface;
29+
30+
use function intval;
31+
32+
class AccordionItemDirective extends SubDirective
33+
{
34+
public const NAME = 'accordion-item';
35+
36+
public function __construct(
37+
protected Rule $startingRule,
38+
GenericLinkProvider $genericLinkProvider,
39+
private readonly AnchorNormalizer $anchorReducer,
40+
private readonly LoggerInterface $logger,
41+
) {
42+
parent::__construct($startingRule);
43+
44+
$genericLinkProvider->addGenericLink(self::NAME, CardNode::LINK_TYPE, CardNode::LINK_PREFIX);
45+
}
46+
47+
public function getName(): string
48+
{
49+
return self::NAME;
50+
}
51+
52+
protected function processSub(
53+
BlockContext $blockContext,
54+
CollectionNode $collectionNode,
55+
Directive $directive,
56+
): Node|null {
57+
$headerLevel = intval($directive->getOption('header-level')->getValue());
58+
if ($headerLevel <= 0) {
59+
$headerLevel = 3;
60+
}
61+
62+
if ($directive->getDataNode() !== null) {
63+
$title = new TitleNode($directive->getDataNode(), $headerLevel, $this->getName());
64+
} else {
65+
$title = TitleNode::fromString('Accordion Item')->setLevel($headerLevel);
66+
$this->logger->warning('An accordion item must have a title. Usage: .. accordion-item:: [title] ', $blockContext->getLoggerInformation());
67+
}
68+
69+
$children = $collectionNode->getChildren();
70+
71+
$id = $directive->getOption('name')->toString();
72+
$show = $directive->hasOption('show');
73+
if ($id === '') {
74+
$id = 'accordion';
75+
$this->logger->warning('An accordion item must have a unique name as parameter. ', $blockContext->getLoggerInformation());
76+
}
77+
78+
$id = $this->anchorReducer->reduceAnchor($id);
79+
80+
return new AccordionItemNode(
81+
$this->getName(),
82+
$directive->getData(),
83+
$directive->getDataNode() ?? new InlineCompoundNode(),
84+
$title,
85+
$children,
86+
$id,
87+
$show,
88+
);
89+
}
90+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Bootstrap\Nodes;
15+
16+
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
17+
use phpDocumentor\Guides\Nodes\LinkTargetNode;
18+
use phpDocumentor\Guides\Nodes\Node;
19+
use phpDocumentor\Guides\Nodes\OptionalLinkTargetsNode;
20+
use phpDocumentor\Guides\Nodes\PrefixedLinkTargetNode;
21+
use phpDocumentor\Guides\Nodes\TitleNode;
22+
use phpDocumentor\Guides\RestructuredText\Nodes\GeneralDirectiveNode;
23+
24+
final class AccordionItemNode extends GeneralDirectiveNode implements LinkTargetNode, OptionalLinkTargetsNode, PrefixedLinkTargetNode
25+
{
26+
public const LINK_TYPE = 'std:accordion';
27+
public const LINK_PREFIX = 'accordion-';
28+
29+
/** @param list<Node> $value */
30+
public function __construct(
31+
protected readonly string $name,
32+
protected readonly string $plainContent,
33+
protected readonly InlineCompoundNode $content,
34+
protected readonly TitleNode $title,
35+
array $value = [],
36+
private readonly string $id = '',
37+
private readonly bool $show = false,
38+
) {
39+
parent::__construct($name, $plainContent, $content, $value);
40+
}
41+
42+
public function getTitle(): TitleNode
43+
{
44+
return $this->title;
45+
}
46+
47+
public function getName(): string
48+
{
49+
return $this->name;
50+
}
51+
52+
public function getPlainContent(): string
53+
{
54+
return $this->plainContent;
55+
}
56+
57+
public function getContent(): InlineCompoundNode
58+
{
59+
return $this->content;
60+
}
61+
62+
public function getLinkType(): string
63+
{
64+
return self::LINK_TYPE;
65+
}
66+
67+
public function getId(): string
68+
{
69+
return $this->id;
70+
}
71+
72+
public function getAnchor(): string
73+
{
74+
return self::LINK_PREFIX . $this->id;
75+
}
76+
77+
public function getLinkText(): string
78+
{
79+
return $this->getTitle()->toString();
80+
}
81+
82+
public function isNoindex(): bool
83+
{
84+
return $this->id === '';
85+
}
86+
87+
public function getPrefix(): string
88+
{
89+
return self::LINK_PREFIX;
90+
}
91+
92+
public function isShow(): bool
93+
{
94+
return $this->show;
95+
}
96+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Bootstrap\Nodes;
15+
16+
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
17+
use phpDocumentor\Guides\Nodes\Node;
18+
use phpDocumentor\Guides\RestructuredText\Nodes\GeneralDirectiveNode;
19+
20+
final class AccordionNode extends GeneralDirectiveNode
21+
{
22+
/** @param list<Node> $value */
23+
public function __construct(
24+
protected readonly string $name,
25+
protected readonly string $plainContent,
26+
protected readonly InlineCompoundNode $content,
27+
array $value = [],
28+
protected readonly string $id = 'accordion',
29+
) {
30+
parent::__construct($name, $plainContent, $content, $value);
31+
}
32+
33+
public function getId(): string
34+
{
35+
return $this->id;
36+
}
37+
38+
public function getAnchor(): string
39+
{
40+
return 'accordion-parent-' . $this->id;
41+
}
42+
}

packages/guides/src/Nodes/TitleNode.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ public function getLevel(): int
4040
return $this->level;
4141
}
4242

43+
public function setLevel(int $level): TitleNode
44+
{
45+
$this->level = $level;
46+
47+
return $this;
48+
}
49+
4350
public function setTarget(string $target): void
4451
{
4552
$this->target = $target;

0 commit comments

Comments
 (0)