-
-
Notifications
You must be signed in to change notification settings - Fork 356
[Twig] Allow nested attributes #1405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1058,6 +1058,63 @@ Exclude specific attributes: | |
My Component! | ||
</div> | ||
|
||
Nested Attributes | ||
~~~~~~~~~~~~~~~~~ | ||
|
||
.. versionadded:: 2.17 | ||
|
||
The Nested Attributes feature was added in TwigComponents 2.17. | ||
|
||
You can have attributes that aren't meant to be used on the *root* element | ||
but one of its *descendants*. This is useful for, say, a dialog component where | ||
you want to allow customizing the attributes of the dialog's content, title, | ||
and footer. Here's an example of this: | ||
|
||
.. code-block:: html+twig | ||
|
||
{# templates/components/Dialog.html.twig #} | ||
<div{{ attributes }}> | ||
<div{{ attributes.nested('title') }}> | ||
{% block title %}Default Title{% endblock %} | ||
</div> | ||
<div{{ attributes.nested('body') }}> | ||
{% block content %}{% endblock %} | ||
</div> | ||
<div{{ attributes.nested('footer') }}> | ||
{% block footer %}Default Footer{% endblock %} | ||
</div> | ||
</div> | ||
|
||
{# render #} | ||
<twig:Dialog class="foo" title:class="bar" body:class="baz" footer:class="qux"> | ||
Some content | ||
</twig:MyDialog> | ||
|
||
{# output #} | ||
<div class="foo"> | ||
<div class="bar"> | ||
Default Title | ||
</div> | ||
<div class="baz"> | ||
Some content | ||
</div> | ||
<div class="qux"> | ||
Default Footer | ||
</div> | ||
</div> | ||
|
||
The nesting is recursive so you could potentially do something like this: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm on very small details but this is not really recursive and more a tree here (in the meaning a parent call child, but never call itself back) This paragraph and your following example make me want to do two small TwigComponent demos
WDYT ? (and we need to start the TwigComponent demo) |
||
|
||
.. code-block:: html+twig | ||
|
||
<twig:Form | ||
:form="form" | ||
class="ui-form" | ||
row:class="ui-form-row" | ||
row:label:class="ui-form-label" | ||
row:widget:class="ui-form-widget" | ||
/> | ||
|
||
Component with Complex Variants (CVA) | ||
------------------------------------- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,8 +19,10 @@ | |
* | ||
* @immutable | ||
*/ | ||
final class ComponentAttributes implements \IteratorAggregate, \Countable | ||
final class ComponentAttributes implements \Stringable, \IteratorAggregate, \Countable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. <3 |
||
{ | ||
private const NESTED_REGEX = '#^([\w-]+):(.+)$#'; | ||
|
||
/** @var array<string,true> */ | ||
private array $rendered = []; | ||
|
||
|
@@ -39,6 +41,10 @@ public function __toString(): string | |
fn (string $key) => !isset($this->rendered[$key]) | ||
), | ||
function (string $carry, string $key) { | ||
if (preg_match(self::NESTED_REGEX, $key)) { | ||
return $carry; | ||
} | ||
|
||
$value = $this->attributes[$key]; | ||
|
||
if ($value instanceof \Stringable) { | ||
|
@@ -196,6 +202,19 @@ public function remove($key): self | |
return new self($attributes); | ||
} | ||
|
||
public function nested(string $namespace): self | ||
{ | ||
$attributes = []; | ||
|
||
foreach ($this->attributes as $key => $value) { | ||
if (preg_match(self::NESTED_REGEX, $key, $matches) && $namespace === $matches[1]) { | ||
$attributes[$matches[2]] = $value; | ||
} | ||
} | ||
|
||
return new self($attributes); | ||
} | ||
|
||
public function getIterator(): \Traversable | ||
{ | ||
return new \ArrayIterator($this->attributes); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<div{{ attributes }}/> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<main{{ attributes }}> | ||
<div{{ attributes.nested('title') }}> | ||
<span{{ attributes.nested('title').nested('span') }}> | ||
{{ component('JustAttributes', [...attributes.nested('inner')]) }} | ||
</span> | ||
</div> | ||
</main> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can pass attributes from the outside directly to the descendants of a component. This approach is especially useful for components like dialogs, to configure the rendering of inner parts like content, title, and footer. Here's an example: