a-h / templ

A language for writing HTML user interfaces in Go.
https://templ.guide/
MIT License
8.04k stars 264 forks source link

proposal: Typed Children for Wrappers #786

Closed denartha10 closed 1 month ago

denartha10 commented 3 months ago

Feature Enhancement Request: Typed Children for wrappers

Description:

Currently, in the templ framework, we can create an wrapper component that wraps multiple input components. The children of this group are specified using the children ... syntax, which does not enforce the type of children being passed. This proposal aims to enhance type safety by allowing the wrapper component to specify the type of its children, ensuring that only components with a specific signature can be used within the group.

Example Code:

package components

// Define the formInput type as a function signature
type formInput func(label, placeholder, name string) templ.Component

// Define textField component
templ textField(label, placeholder, name string) {
  <label class="block mb-4">
    <span class="text-accent text-sm mb-2">{ label }</span>
    <input
      class="shadow appearance-none border rounded w-full py-2 px-3 text-accent leading-tight focus:outline-none focus:shadow-outline"
      type="text" placeholder={ placeholder } name={ name } />
  </label>
}

// Define passwordField component
templ passwordField(label, placeholder, name string) {
  <label class="block mb-4">
    <span class="text-accent text-sm mb-2">{ label }</span>
    <input
      class="shadow appearance-none border rounded w-full py-2 px-3 text-accent leading-tight focus:outline-none focus:shadow-outline"
      type="password" placeholder={ placeholder } name={ name } />
  </label>
}

// Define emailField component
templ emailField(label, placeholder, name string) {
  <label class="block mb-4">
    <span class="text-accent text-sm mb-2">{ label }</span>
    <input
      class="shadow appearance-none border rounded w-full py-2 px-3 text-accent leading-tight focus:outline-none focus:shadow-outline"
      type="email" placeholder={ placeholder } name={ name } />
  </label>
}

// Define inputGroup component with typed children
templ inputGroup(children ...formInput) {
  gridSize := count(inputs)
  <div>
    { children...  }
  </div>
}
joerdav commented 3 months ago

I don't think I understand the suggested design. What would it look like to render this?

One potential issue with this is that the result of emailField("", "", "") is not func(label, placeholder, name string) templ.Component It is templ.Component.

denartha10 commented 3 months ago

Hi @joerdav, I am new to golang and so am still open to messing up the syntax.

I am loving the templ library at the moment and would like to go forward using it. I am also a big fan of golang and its strong type system. The example I gave above is fairly convoluted so I am trying to think of a better way to explain it

below is my description of how i understand the component structure with children ... syntax

// input group would represent a templ component that also uses the children syntax to render content inside it
templ inputGroup(children ...formInput) {
  gridSize := count(inputs)
  <div>
    { children...  } // right now you can place any valid temple component inside here during rendering
  </div>
}

below is my example of using it elsewhere in a seperate templ file

@inputGroup(){
    // is there a way to type the original inputGroup component so these inputcomponents must be of a certain type
    // Say a templ component with certain parameters?
    @sampleInputcomponent1() 
    @sampleInputcomponent2()
}

I hope I have provided a clearer idea using the above two code snippets. Please ask if I have not been clear and or correct me if my understanding of either go or templ syntax is incorrect

Thanks :)

joerdav commented 1 month ago

Okay, I understand your problem. I don't think children is the solution to this.

In your inputGroup function children is basically of type []formInput, so you could achieve the desired API currently by doing the following:

templ inputGroup(inputs ...formInput) {
  gridSize := count(inputs)
  <div>
    for _, inp := range inputs {
        @inp
    }
  </div>
}

Potentially what could improve this experience is a func Join(components ...templ.Component) templ.Component. Then it could be written as:

templ inputGroup(inputs ...formInput) {
  gridSize := count(inputs)
  <div>
     @templ.Join(inputs...)
  </div>
}

And used as:

@inputGroup(
    sampleInputcomponent1(),
    sampleInputcomponent2(),
)
a-h commented 1 month ago

Sounds like https://github.com/a-h/templ/issues/786#issuecomment-2298507263 is getting at the same idea as https://github.com/a-h/templ/issues/788

I proposed the name Chain, but Concat or Join is better.

Maybe we should add that...

a-h commented 1 month ago

I'll close this as duplicate, I'm pretty sure @joerdav's comment answers this.