templ icon indicating copy to clipboard operation
templ copied to clipboard

please review alert component

Open msonawane opened this issue 1 year ago • 4 comments

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 avatar Dec 06 '23 04:12 msonawane

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

msonawane avatar Dec 06 '23 04:12 msonawane

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

joerdav avatar Dec 06 '23 14:12 joerdav

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.
    }
  }
}

a-h avatar Dec 09 '23 14:12 a-h

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

msonawane avatar Dec 22 '23 07:12 msonawane

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!

a-h avatar Jan 05 '24 23:01 a-h