a-h / templ

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

please review alert component #311

Closed msonawane closed 8 months ago

msonawane commented 9 months ago

I would like to get reviews and suggestions for this simple alert component based on tabler.io which in turn is based on bootstrap 5. I think more guides should be available for best practices, and we can have an opensource components if anyone wants to join hands :)

package components

type AlertOptions struct {
    Icon        templ.Component
    Title       string
    Text        string
    Classes     string
    Success     bool
    Info        bool
    Warning     bool
    Danger      bool
    Dismissible bool
}

var (
    DangerAlertOpts = AlertOptions{
        Icon:        AlertTriangleSVG(),
        Title:       "Alert Title",
        Text:        "Alert Test",
        Dismissible: true,
        Danger:      true,
    }

    SuccessAlertOpts = AlertOptions{
        Icon:        CheckSVG(),
        Title:       "Alert Title",
        Text:        "Alert Test",
        Dismissible: true,
        Success:     true,
    }
    InfoAlertOpts = AlertOptions{
        Icon:        InfoCircleSVG(),
        Title:       "Alert Title",
        Text:        "Alert Text",
        Dismissible: true,
        Info:        true,
    }

    WarningAlertOpts = AlertOptions{
        Icon:        AlertTriangleSVG(),
        Title:       "Alert Title",
        Text:        "Alert Text",
        Dismissible: true,
        Warning:     true,
    }
)

templ Alert(opt AlertOptions) {
    <div
        class={
            "alert",
            templ.KV("alert-dismissible", opt.Dismissible),
            templ.KV("alert-success", opt.Success),
            templ.KV("alert-info", opt.Info),
            templ.KV("alert-danger", opt.Danger),
            templ.KV("alert-warning", opt.Warning),
            opt.Classes,
        }
        role="alert"
    >
        <div class="d-flex">
            if opt.Icon != nil {
                <div class="alert-icon">
                    @opt.Icon
                </div>
            }
            <div>
                <h4 class="alert-title">{ opt.Title }</h4>
                <div class="text-secondary">{ opt.Text }</div>
            </div>
            <a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
        </div>
    </div>
}

usage

successAlertOpt := components.SuccessAlertOpts
successAlertOpt.Title = "Success Title"
successAlertOpt.Text = "Success Text"
components.Alert(successAlertOpt)
msonawane commented 9 months ago

please feel free to join hands here https://github.com/msonawane/templ-components

joerdav commented 9 months ago

Hi I think this post would be great for in the templ slack channel that lives in the gopher server :)

a-h commented 9 months ago

Brilliant. Making component libraries to compose UIs is exactly what I hoped people would do with templ.

I was hoping we'd build something like Chakra UI, and we could plug components together to make layouts, forms and visual displays.

If you look in the templ repo, there's Storybook support in there, but I haven't documented it, because Storybook introduced a bug, so I needed to wait for it to be fixed (I think it's fixed now). There's an example at https://github.com/a-h/templ/tree/main/storybook/_example

The example lets you have a website where you can interact with templ components in realtime. It uses AWS Lambda, so it's cheap to run.

Happy to help you get started with that, if it's something you're interested in.

In terms of suggestions for the component, I'd suggest calling AlertOptions AlertProps instead, because options in Go are usually the functional options, i.e. you'd do something like func Alert(opts ...AlertOpt) templ.Component and use that pattern.

I'm not sure it's better, but if you did want to use functional opts style, then this would work.

package tcalert

type Opt func(*Props)

func WithProps(props *Props string) Opt {
  return func(p *Props) {
    p = props
  }
}

func Title(title string) Opt {
  return func(a *Props) {
    a.Title = title
  }
}

type Props struct {
  Title string
}

templ component(props Props) {
  // The component HTML etc.
}

func New(opts ...Opt) templ.Component {
  props := &Props{}
  for _, opt := range opts {
    opt(props)
  }
  //TODO: Set defaults for stuff that can't be left out.
  // e.g. if props.Image == nil { props.Image = default }
  return component(props)
}

And you'd have something like this when you use it.

@tcalert.New(tcalert.Title("Help!"))

And you can still do plain props if you want.

@tcalert.New(tcalert.WithProps(&tcalert.Props{ // values }))

In terms of naming things, Go modules are best without hyphens in them, and usually short, since they will form part of the code for users.

In the example above, I've called it tcalert for templ components and alert. I don't think templ components is a great name, because it clashes with the "concept" of templ components, and so it will likely confuse people. Calling it something like mc for msonawane components, and then it being @mcalert.New(mcalert.WithSeverity(mcalert.SeverityError))) would be cool.

I didn't call it plain "alert" because "alert" is a variable name that a user of the component might want to use. I think it's bad form for library authors to take good names away. In the worst case, users then end up having to rename imports, which is hassle for them.

The functional pattern helps because you can then use a single option to set multiple fields in the Props struct, e.g.

type Severity int

const (
  SeverityInfo Severity = iota
  SeverityWarning Severity = 1
  SeverityError Severity = 2
)

func WithSeverity(severity Severity) Opt {
  return func(p *Props) {
    switch(severity) {
      case SeverityInfo:
        p.Class = "info"
        p.Icon = "info-icon"
      //TODO: Add other cases.
    }
  }
}
msonawane commented 8 months ago

wow thanks for the wisdom i will digest it and ask any questions over the weekend

a-h commented 8 months ago

I'll close this, since it's not an issue. Let's move discussion to a Github discussion or the Slack channel.

The discussion point would be "Creating a templ UI component library", I think!