Skip to content
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

[TwigComponent] Add aware tag #2522

Open
seb-jean opened this issue Jan 23, 2025 · 28 comments
Open

[TwigComponent] Add aware tag #2522

seb-jean opened this issue Jan 23, 2025 · 28 comments

Comments

@seb-jean
Copy link
Contributor

Hi,

This issue is a continuation of #2514.

I looked at how this was done for Laravel components. It uses an @aware directive to pass props from a parent component to a child component. This also allows us to say which props will be accessible or not for children. This works for self closing tags but also for opening and closing tags.

@WebMamba
Copy link
Contributor

I think every component should be isolated as much as possible. You should not have components depending on the external context. Why not just use props?

@Kocal
Copy link
Member

Kocal commented Jan 23, 2025

100% agree here, good components must stay isolated the more as possible.

@seb-jean
Copy link
Contributor Author

The idea behind this is to avoid repeating the props on each child component like this:

<twig:Form:Row disabled>
    <twig:Form:Label disabled>Description</twig:Form:Label>
    <twig:Form:Textarea disabled />
    <twig:Form:Description disabled>Symfony UX Core Team is awesome!</twig:Form:Description>
</twig:Form:Row>

@smnandre
Copy link
Member

So a sort of temporary data-store, during the render.

That would not be "passed down" but more... "available anywhere while the parent component is rendering" .. right ?

@smnandre
Copy link
Member

@seb-jean : is the sample you provide a real-world one ? Like here, why would you need to "pass down" the disabled attribute ? (genuine question i'm looking for examples)

@seb-jean
Copy link
Contributor Author

@smnandre
Copy link
Member

Disabled can be set on the highest form element to disable everything below, not sure if this is a good example (checkbox disabled would need to be identified)

For the rest, have I understood correctly for the "inside share context" ?

@seb-jean
Copy link
Contributor Author

Yes, you understood correctly :).

@Kocal
Copy link
Member

Kocal commented Jan 23, 2025

@smnandre
Copy link
Member

Hmm.. not exactly. I mean what i had in mind, but we could thing anything. Just be aware (pun intended) this will hurt our brain the second we need to prioritize things.

So what i had in mind was more the context for the content (so anything in Twig)

If i look at your examples and the one @Kocal shared, we are then speaking about mounting data as props.

So what would we what ?

@WebMamba
Copy link
Contributor

What we can do is a bit inspired by React. It is to access the child's parent context via a variable.
So in your child, you could do something like this:

{{ parent.disabled }}

WDYT ?

@WebMamba
Copy link
Contributor

But I am against giving the ability to share context between parent and child components directly. For the reasons explain above.

@seb-jean
Copy link
Contributor Author

{{ parent.disabled }} is clean. However, it should not be only the direct parent.
What I found interesting for @aware is to make available the parent props on a case-by-case basis.

This is mainly to avoid adding props everywhere in the components. For my part, I find that it does not make the code easier to read to repeat the props. This is my point of view.

@seb-jean
Copy link
Contributor Author

Here is another example, this time from Primer Design System:
https://primer.style/components/form-control/react/alpha

In the demos, you can select "With caption and disabled", and then view the code. It's the same principle.

@smnandre
Copy link
Member

What we can do is a bit inspired by React. It is to access the child's parent context via a variable. So in your child, you could do something like this:

{{ parent.disabled }}

WDYT ?

You mean something like....

 {{ outerscope.disabled }}

?

Noo.... that'd be crazy, right ? 🌻

@smnandre
Copy link
Member

On a more serious note, couple of comments here.

Why pass down a value to every child?

Here is another example, this time from Primer Design System: https://primer.style/components/form-control/react/alpha

In the demos, you can select "With caption and disabled", and then view the code. It's the same principle.

There's something I don't quite understand here. What you want to do is pass the "disabled" state once to a component, and it would then be disabled, correct? So, from the main template (outside the form:fieldset), you'd do this:

<twig:form:fieldset     form="my_form"   disabled   />

And every fieldset or input control inside it would also be disabled, right?

So here's my question: why would you pass the same value down to all its elements?

For instance, in HTML, when you use disabled, it automatically disables any child element "deeper" in the DOM tree. There's no need to propagate it manually.

Do you have a real-world example where you're trying to use this feature, or is this more of a "wish" based on what other component kits or UI frameworks offer? (I'm genuinely curious to understand, nothing else! 😉)


Component / Template / HTML

I make no assumptions about whether the piece of code you’re showing is real, a simplified example, or something commonly found in templates.

But this usage is very weird to me right now.

<twig:Form:Row disabled>
    <twig:Form:Label disabled>Description</twig:Form:Label>
    <twig:Form:Textarea disabled />
    <twig:Form:Description disabled>Symfony UX Core Team is awesome!</twig:Form:Description>
</twig:Form:Row>

Meaning you have a row component that has no chikd oer defayult ? So what its it ? I mean what is the essence of this component ? It could quickyl be taken as a wrapper... a html tag , right ?

Inversely, if you have per default the 3 other (lavel/tex/descr) inside... what don't you define this in the component template. Like set the 5 attributes you'd want to pass down and ... use them

{# component template Form.Row.html.twig  #}
...
    <twig:Form:Label  {{ ...  args }}>You're awesome too!</twig:Form:Label> 
    <twig:Form:Textarea  {{ ...  args }}   />
    <twig:Form:Description  {{ ...  args }}   />

And this is done, good.

Because here, as you render the Label, Textarea and Descrption from the block content of the Row, you have to pass their values ? So it would more look like this

Do you need to write the label/textarea/description to every tilme you need a form row ?

<twig:Form:Row disabled value={{ aaaa }} data-id="{{ ;;; }}" >
    <twig:Form:Label disabled for="aaa">Description</twig:Form:Label>
    <twig:Form:Textarea disabled value={{ textarea.value}}  name="foo"  for="aaa" resize=off />
    <twig:Form:Description disabled aria-foo=bar clas="fsfdd">Symfony UX Core Team is awesome!</twig:Form:Description>
</twig:Form:Row>

Please tell me if I'm way off.. !

@norkunas
Copy link
Contributor

You talk here about forms only.. In a complex layouts that has parent <-> children with some props that customizes layout and more than 1 level it'd be nice to have this, instead of repeating it every time :)

@smnandre
Copy link
Member

Any particular example here to help me ? Because I cant think of one

(and have almost no experience with js component framework)

@norkunas
Copy link
Contributor

norkunas commented Jan 24, 2025

Most simple one that I could find fast on mobile https://mui.com/material-ui/react-button-group/#basic-button-group where you provide the button variant to the group, also size could be controlled same way

@smnandre
Copy link
Member

Ok so --really tell me if i miss understand this-- what you want here is to bing the concept of "wrapping" component, rght ?

@seb-jean
Copy link
Contributor Author

There's something I don't quite understand here. What you want to do is pass the "disabled" state once to a component, and it would then be disabled, correct? So, from the main template (outside the form:fieldset), you'd do this:

And every fieldset or input control inside it would also be disabled, right?

What I want to do is pass a "disabled" state to a component, and I could use it on child components. For example, to modify the CSS or assign the disabled attribute. On a label element, I won't need to have the disabled attribute, but on the other hand, I will need to modify the CSS.

Do you have a real-world example where you're trying to use this feature, or is this more of a "wish" based on what other component kits or UI frameworks offer? (I'm genuinely curious to understand, nothing else! 😉)

This is a wish based on UI kit (https://catalyst.tailwindui.com).

Meaning you have a row component that has no chikd oer defayult ? So what its it ? I mean what is the essence of this component ? It could quickyl be taken as a wrapper... a html tag , right ?

Component Row:

<div {{ attributes.defaults({
    class: html_classes(
        '[&>[data-slot=label]+[data-slot=control]]:mt-3',
        '[&>[data-slot=label]+[data-slot=description]]:mt-1',
        '[&>[data-slot=description]+[data-slot=control]]:mt-3',
        '[&>[data-slot=control]+[data-slot=description]]:mt-3',
        '[&>[data-slot=control]+[data-slot=error]]:mt-3',
        '*:data-[slot=label]:font-medium'
    )
}) }}>
    {% block content %}{% endblock %}
</div>

Indeed, I don't have a child by default but rather a block content.

Do you need to write the label/textarea/description to every tilme you need a form row ?

I need to write the Label, Textarea (or Input), Description every time I write a form row. I could just have an Input component or just a Textarea component without a Label. It gives flexibility.

And if it's easier to discuss this on Slack, we can plan that :).

@smnandre
Copy link
Member

Yeah we'll do that !

Just two last questions, in order to complete the picture, if you agree.

  1. do you often have this pattern/need ?

(a tag that "only" defines attributes: its innerHTML is filled by the "content" block)

Like it happens from time to time, or is this a frequent thing ? (I try to understand if this is a rare use-case or something almost systematic)

  1. could you tell me, in the following situations, if you use twig tags: "never", "sometimes", always"
  • In your main templates, outside components
  • In your templates, but in the components content block
  • In your cmponent templates

Here i'd like to estimate if there are "components only area" and "twig only areas", or if all is on a gray zone.

@norkunas if you have a feedback on this too, i'd be happy to ear it :)

@seb-jean
Copy link
Contributor Author

seb-jean commented Jan 27, 2025

Do you often have this pattern/need?

Yes, I often use this pattern with components, it gives me a lot of flexibility.

To answer the second question, I have 2 questions too:

What do you mean by twig tags? Is that right: set, if, block, for, macro, extends, ...?

Similarly, when you say "In your templates, but in the components content block", is that something like this?

<twig:Alert size="xl">
    {% if type == 'success' %}
        {% set color = 'green' %}
    {% else %}
        {% set color = 'red' %}
    {% ending %}
    <div class="{{ color }}">Symfony is magic ✨</div>
</twig:Alert>

Thanks

@smnandre
Copy link
Member

Yes
Yes

:)

@norkunas
Copy link
Contributor

@norkunas if you have a feedback on this too, i'd be happy to ear it :)

I won't convince, but at least for me this would be a nice helper to get work done faster, but I am already used to live without this 🙂

@WebMamba
Copy link
Contributor

@norkunas @seb-jean did you try the outerscope , and the outerblock variables: https://symfony.com/bundles/ux-twig-component/current/index.html#context-variables-inside-of-blocks ?
With the outerblock variable you have access to your parent context, but without mixing the parent and the child context WDYT?

@seb-jean
Copy link
Contributor Author

seb-jean commented Jan 27, 2025

Could you tell me, in the following situations, if you use twig tags: "never", "sometimes", always"

  • In your main templates, outside components:
    sometimes/always, it depends on the template.
  • In your templates, but in the components content block:
    never
  • In your component templates:
    sometimes/always, it depends on the component

@WebMamba , I did indeed try the solution you suggested but it didn't work.

@seb-jean
Copy link
Contributor Author

seb-jean commented Feb 9, 2025

I just did a test to look at the Laravel side, the prop went from a parent component to deep children and not just the first child.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants