Migrate from templates to Gomponents (#103)
This commit is contained in:
parent
0bf9ab7189
commit
051d032038
104 changed files with 2768 additions and 2824 deletions
64
pkg/ui/components/alerts.go
Normal file
64
pkg/ui/components/alerts.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"github.com/mikestefanello/pagoda/pkg/msg"
|
||||
"github.com/mikestefanello/pagoda/pkg/ui"
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func FlashMessages(r *ui.Request) Node {
|
||||
var g Group
|
||||
for _, typ := range []msg.Type{
|
||||
msg.TypeSuccess,
|
||||
msg.TypeInfo,
|
||||
msg.TypeWarning,
|
||||
msg.TypeDanger,
|
||||
} {
|
||||
for _, str := range msg.Get(r.Context, typ) {
|
||||
g = append(g, Notification(typ, str))
|
||||
}
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func Notification(typ msg.Type, text string) Node {
|
||||
var class string
|
||||
|
||||
switch typ {
|
||||
case msg.TypeSuccess:
|
||||
class = "success"
|
||||
case msg.TypeInfo:
|
||||
class = "info"
|
||||
case msg.TypeWarning:
|
||||
class = "warning"
|
||||
case msg.TypeDanger:
|
||||
class = "danger"
|
||||
}
|
||||
|
||||
return Div(
|
||||
Class("notification is-"+class),
|
||||
Attr("x-data", "{show: true}"),
|
||||
Attr("x-show", "show"),
|
||||
Button(
|
||||
Class("delete"),
|
||||
Attr("@click", "show = false"),
|
||||
),
|
||||
Text(text),
|
||||
)
|
||||
}
|
||||
|
||||
func Message(class, header string, body Node) Node {
|
||||
return Article(
|
||||
Class("message "+class),
|
||||
If(header != "", Div(
|
||||
Class("message-header"),
|
||||
P(Text(header)),
|
||||
)),
|
||||
Div(
|
||||
Class("message-body"),
|
||||
body,
|
||||
),
|
||||
)
|
||||
}
|
||||
203
pkg/ui/components/form.go
Normal file
203
pkg/ui/components/form.go
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"github.com/mikestefanello/pagoda/pkg/form"
|
||||
"github.com/mikestefanello/pagoda/pkg/ui"
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
type (
|
||||
InputFieldParams struct {
|
||||
Form form.Form
|
||||
FormField string
|
||||
Name string
|
||||
InputType string
|
||||
Label string
|
||||
Value string
|
||||
Placeholder string
|
||||
Help string
|
||||
}
|
||||
|
||||
RadiosParams struct {
|
||||
Form form.Form
|
||||
FormField string
|
||||
Name string
|
||||
Label string
|
||||
Value string
|
||||
Options []Radio
|
||||
}
|
||||
|
||||
Radio struct {
|
||||
Value string
|
||||
Label string
|
||||
}
|
||||
|
||||
TextareaFieldParams struct {
|
||||
Form form.Form
|
||||
FormField string
|
||||
Name string
|
||||
Label string
|
||||
Value string
|
||||
Help string
|
||||
}
|
||||
)
|
||||
|
||||
func ControlGroup(controls ...Node) Node {
|
||||
g := make(Group, len(controls))
|
||||
for i, control := range controls {
|
||||
g[i] = Div(
|
||||
Class("control"),
|
||||
control,
|
||||
)
|
||||
}
|
||||
|
||||
return Div(
|
||||
Class("field is-grouped"),
|
||||
g,
|
||||
)
|
||||
}
|
||||
|
||||
func TextareaField(el TextareaFieldParams) Node {
|
||||
return Div(
|
||||
Class("field"),
|
||||
Label(
|
||||
For("name"),
|
||||
Class("label"),
|
||||
Text(el.Label),
|
||||
),
|
||||
Div(
|
||||
Class("control"),
|
||||
Textarea(
|
||||
ID(el.Name),
|
||||
Name(el.Name),
|
||||
Class("textarea "+formFieldStatusClass(el.Form, el.FormField)),
|
||||
Text(el.Value),
|
||||
),
|
||||
),
|
||||
If(el.Help != "", P(Class("help"), Text(el.Help))),
|
||||
formFieldErrors(el.Form, el.FormField),
|
||||
)
|
||||
}
|
||||
|
||||
func Radios(el RadiosParams) Node {
|
||||
buttons := make(Group, len(el.Options))
|
||||
for i, opt := range el.Options {
|
||||
buttons[i] = Label(
|
||||
Class("radio"),
|
||||
Input(
|
||||
Type("radio"),
|
||||
Name(el.Name),
|
||||
Value(opt.Value),
|
||||
If(el.Value == opt.Value, Checked()),
|
||||
),
|
||||
Text(" "+opt.Label),
|
||||
)
|
||||
}
|
||||
|
||||
return Div(
|
||||
Class("control field"),
|
||||
Label(Class("label"), Text(el.Label)),
|
||||
Div(
|
||||
Class("radios"),
|
||||
buttons,
|
||||
),
|
||||
formFieldErrors(el.Form, el.FormField),
|
||||
)
|
||||
}
|
||||
|
||||
func InputField(el InputFieldParams) Node {
|
||||
return Div(
|
||||
Class("field"),
|
||||
Label(
|
||||
Class("label"),
|
||||
For(el.Name),
|
||||
Text(el.Label),
|
||||
),
|
||||
Div(
|
||||
Class("control"),
|
||||
Input(
|
||||
ID(el.Name),
|
||||
Name(el.Name),
|
||||
Type(el.InputType),
|
||||
If(el.Placeholder != "", Placeholder(el.Placeholder)),
|
||||
Class("input "+formFieldStatusClass(el.Form, el.FormField)),
|
||||
Value(el.Value),
|
||||
),
|
||||
),
|
||||
If(el.Help != "", P(Class("help"), Text(el.Help))),
|
||||
formFieldErrors(el.Form, el.FormField),
|
||||
)
|
||||
}
|
||||
|
||||
func FileField(name, label string) Node {
|
||||
return Div(
|
||||
Class("field file"),
|
||||
Label(
|
||||
Class("file-label"),
|
||||
Input(
|
||||
Class("file-input"),
|
||||
Type("file"),
|
||||
Name(name),
|
||||
),
|
||||
Span(
|
||||
Class("file-cta"),
|
||||
Span(
|
||||
Class("file-label"),
|
||||
Text(label),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func formFieldStatusClass(fm form.Form, formField string) string {
|
||||
switch {
|
||||
case !fm.IsSubmitted():
|
||||
return ""
|
||||
case fm.FieldHasErrors(formField):
|
||||
return "is-danger"
|
||||
default:
|
||||
return "is-success"
|
||||
}
|
||||
}
|
||||
|
||||
func formFieldErrors(fm form.Form, field string) Node {
|
||||
errs := fm.GetFieldErrors(field)
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
g := make(Group, len(errs))
|
||||
for i, err := range errs {
|
||||
g[i] = P(
|
||||
Class("help is-danger"),
|
||||
Text(err),
|
||||
)
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func CSRF(r *ui.Request) Node {
|
||||
return Input(
|
||||
Type("hidden"),
|
||||
Name("csrf"),
|
||||
Value(r.CSRF),
|
||||
)
|
||||
}
|
||||
|
||||
func FormButton(class, label string) Node {
|
||||
return Button(
|
||||
Class("button "+class),
|
||||
Text(label),
|
||||
)
|
||||
}
|
||||
|
||||
func ButtonLink(href, class, label string) Node {
|
||||
return A(
|
||||
Href(href),
|
||||
Class("button "+class),
|
||||
Text(label),
|
||||
)
|
||||
}
|
||||
60
pkg/ui/components/head.go
Normal file
60
pkg/ui/components/head.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mikestefanello/pagoda/pkg/ui"
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func JS(r *ui.Request) Node {
|
||||
const htmxErr = `
|
||||
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
||||
if (evt.detail.xhr.status >= 400){
|
||||
evt.detail.shouldSwap = true;
|
||||
evt.detail.target = htmx.find("body");
|
||||
}
|
||||
});
|
||||
`
|
||||
|
||||
const htmxCSRF = `
|
||||
document.body.addEventListener('htmx:configRequest', function(evt) {
|
||||
if (evt.detail.verb !== "get") {
|
||||
evt.detail.parameters['csrf'] = '%s';
|
||||
}
|
||||
})
|
||||
`
|
||||
|
||||
var csrf Node
|
||||
|
||||
if len(r.CSRF) > 0 {
|
||||
csrf = Script(Raw(fmt.Sprintf(htmxCSRF, r.CSRF)))
|
||||
}
|
||||
|
||||
return Group{
|
||||
Script(Src("https://unpkg.com/htmx.org@2.0.0/dist/htmx.min.js")),
|
||||
Script(Src("https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"), Defer()),
|
||||
Script(Raw(htmxErr)),
|
||||
csrf,
|
||||
}
|
||||
}
|
||||
|
||||
func CSS() Node {
|
||||
return Link(
|
||||
Href("https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"),
|
||||
Rel("stylesheet"),
|
||||
)
|
||||
}
|
||||
|
||||
func Metatags(r *ui.Request) Node {
|
||||
return Group{
|
||||
Meta(Charset("utf-8")),
|
||||
Meta(Name("viewport"), Content("width=device-width, initial-scale=1")),
|
||||
Link(Rel("icon"), Href(ui.File("favicon.png"))),
|
||||
TitleEl(Text(r.Config.App.Name), If(r.Title != "", Text(" | "+r.Title))),
|
||||
If(r.Metatags.Description != "", Meta(Name("description"), Content(r.Metatags.Description))),
|
||||
If(len(r.Metatags.Keywords) > 0, Meta(Name("keywords"), Content(strings.Join(r.Metatags.Keywords, ", ")))),
|
||||
}
|
||||
}
|
||||
9
pkg/ui/components/htmx.go
Normal file
9
pkg/ui/components/htmx.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
. "maragu.dev/gomponents"
|
||||
)
|
||||
|
||||
func HxBoost() Node {
|
||||
return Attr("hx-boost", "true")
|
||||
}
|
||||
19
pkg/ui/components/nav.go
Normal file
19
pkg/ui/components/nav.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"github.com/mikestefanello/pagoda/pkg/ui"
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func MenuLink(r *ui.Request, title, routeName string, routeParams ...any) Node {
|
||||
href := r.Path(routeName, routeParams...)
|
||||
|
||||
return Li(
|
||||
A(
|
||||
Href(href),
|
||||
Text(title),
|
||||
If(href == r.CurrentPath, Class("is-active")),
|
||||
),
|
||||
)
|
||||
}
|
||||
56
pkg/ui/components/tabs.go
Normal file
56
pkg/ui/components/tabs.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
type Tab struct {
|
||||
Title, Body string
|
||||
}
|
||||
|
||||
func Tabs(heading, description string, items []Tab) Node {
|
||||
renderTitles := func() Node {
|
||||
g := make(Group, len(items))
|
||||
for i, item := range items {
|
||||
g[i] = Li(
|
||||
Attr(":class", fmt.Sprintf("{'is-active': tab === %d}", i)),
|
||||
Attr("@click", fmt.Sprintf("tab = %d", i)),
|
||||
A(Text(item.Title)),
|
||||
)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
renderBodies := func() Node {
|
||||
g := make(Group, len(items))
|
||||
for i, item := range items {
|
||||
g[i] = Div(
|
||||
Attr("x-show", fmt.Sprintf("tab == %d", i)),
|
||||
P(Raw(" "+item.Body)),
|
||||
)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
return Div(
|
||||
P(
|
||||
Class("subtitle mt-5"),
|
||||
Text(heading),
|
||||
),
|
||||
P(
|
||||
Class("mb-4"),
|
||||
Text(description),
|
||||
),
|
||||
Div(
|
||||
Attr("x-data", "{tab: 0}"),
|
||||
Div(
|
||||
Class("tabs"),
|
||||
Ul(renderTitles()),
|
||||
),
|
||||
renderBodies(),
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue