mirror of
https://github.com/eduardolat/pgbackweb.git
synced 2026-05-12 14:38:28 -05:00
Add basic web components
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package component
|
||||
|
||||
import "github.com/orsinium-labs/enum"
|
||||
|
||||
type size enum.Member[string]
|
||||
|
||||
var (
|
||||
SizeSm = size{"sm"}
|
||||
SizeMd = size{"md"}
|
||||
SizeLg = size{"lg"}
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"github.com/maragudk/gomponents"
|
||||
gcomponents "github.com/maragudk/gomponents/components"
|
||||
"github.com/maragudk/gomponents/html"
|
||||
)
|
||||
|
||||
// HxLoadingSm returns a small loading indicator.
|
||||
func HxLoadingSm(centered bool, id ...string) gomponents.Node {
|
||||
return hxLoading(centered, SizeSm, id...)
|
||||
}
|
||||
|
||||
// HxLoadingMd returns a loading indicator.
|
||||
func HxLoadingMd(centered bool, id ...string) gomponents.Node {
|
||||
return hxLoading(centered, SizeMd, id...)
|
||||
}
|
||||
|
||||
// HxLoadingLg returns a large loading indicator.
|
||||
func HxLoadingLg(centered bool, id ...string) gomponents.Node {
|
||||
return hxLoading(centered, SizeLg, id...)
|
||||
}
|
||||
|
||||
func hxLoading(centered bool, size size, id ...string) gomponents.Node {
|
||||
pickedID := ""
|
||||
if len(id) > 0 {
|
||||
pickedID = id[0]
|
||||
}
|
||||
|
||||
return html.Div(
|
||||
gomponents.If(
|
||||
pickedID != "",
|
||||
html.ID(pickedID),
|
||||
),
|
||||
html.Class("htmx-indicator"),
|
||||
html.Div(
|
||||
gcomponents.Classes{
|
||||
"flex justify-center items-center": centered,
|
||||
"w-full h-full": true,
|
||||
},
|
||||
|
||||
func() gomponents.Node {
|
||||
switch size {
|
||||
case SizeSm:
|
||||
return SpinnerSm()
|
||||
case SizeMd:
|
||||
return SpinnerMd()
|
||||
case SizeLg:
|
||||
return SpinnerLg()
|
||||
default:
|
||||
return SpinnerMd()
|
||||
}
|
||||
}(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lucide "github.com/eduardolat/gomponents-lucide"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
|
||||
"github.com/google/uuid"
|
||||
"github.com/maragudk/gomponents"
|
||||
"github.com/maragudk/gomponents/components"
|
||||
"github.com/maragudk/gomponents/html"
|
||||
)
|
||||
|
||||
// ModalParams are the props for the Modal component.
|
||||
type ModalParams struct {
|
||||
// ID is the ID of the modal dialog. If empty, a random ID will be generated.
|
||||
ID string
|
||||
// Content is the content of the modal dialog.
|
||||
Content []gomponents.Node
|
||||
// Size is the size of the modal dialog.
|
||||
// Can be "sm", "md", and "lg".
|
||||
// The default is "md".
|
||||
Size size
|
||||
// Title is the title of the modal dialog.
|
||||
// If you need more than a string, use TitleNode instead.
|
||||
Title string
|
||||
// TitleNode is the title of the modal dialog.
|
||||
// If you need only a string, use Title instead.
|
||||
TitleNode gomponents.Node
|
||||
// HTMXIndicator is an optional ID of an HTMX indicator that
|
||||
// should be inserted in the modal header.
|
||||
HTMXIndicator string
|
||||
}
|
||||
|
||||
// ModalResult is the result of creating a modal dialog.
|
||||
type ModalResult struct {
|
||||
// HTML is the modal dialog HTML.
|
||||
HTML gomponents.Node
|
||||
// OpenerAttr is the attribute to add to the element that opens the modal dialog.
|
||||
OpenerAttr gomponents.Node
|
||||
}
|
||||
|
||||
// Modal renders a modal dialog.
|
||||
func Modal(params ModalParams) ModalResult {
|
||||
id := params.ID
|
||||
if id == "" {
|
||||
id = "mo-" + uuid.NewString()
|
||||
}
|
||||
|
||||
openEventName := fmt.Sprintf("%s_open", id)
|
||||
closeEventName := fmt.Sprintf("%s_close", id)
|
||||
openerAttr := gomponents.Attr(
|
||||
"onClick",
|
||||
"event.preventDefault(); window.dispatchEvent(new Event('"+openEventName+"'));",
|
||||
)
|
||||
closerAttr := gomponents.Attr(
|
||||
"onClick",
|
||||
"event.preventDefault(); window.dispatchEvent(new Event('"+closeEventName+"'));",
|
||||
)
|
||||
|
||||
openCode := `document.getElementById("` + id + `").classList.remove("hidden");`
|
||||
closeCode := `document.getElementById("` + id + `").classList.add("hidden");`
|
||||
|
||||
size := SizeMd
|
||||
if params.Size.Value != "" {
|
||||
size = params.Size
|
||||
}
|
||||
|
||||
hasHTMXIndicator := params.HTMXIndicator != ""
|
||||
|
||||
content := html.Div(
|
||||
alpine.XData(`{}`),
|
||||
alpine.XOn(fmt.Sprintf("%s.window", openEventName), openCode),
|
||||
alpine.XOn(fmt.Sprintf("%s.window", closeEventName), closeCode),
|
||||
alpine.XOn("keyup.escape.window", closeCode),
|
||||
|
||||
html.ID(id),
|
||||
components.Classes{
|
||||
"hidden": true,
|
||||
"!p-0 !m-0 w-[100dvw] h-[100dvh]": true,
|
||||
"fixed left-0 top-0 z-[1000]": true,
|
||||
},
|
||||
|
||||
// Backdrop
|
||||
html.Div(
|
||||
closerAttr,
|
||||
components.Classes{
|
||||
"bg-black opacity-25": true,
|
||||
"!w-full !h-full": true,
|
||||
"z-[1001]": true,
|
||||
},
|
||||
),
|
||||
|
||||
// Dialog
|
||||
html.Div(
|
||||
components.Classes{
|
||||
"absolute z-[1002] top-[50%] left-[50%]": true,
|
||||
"translate-y-[-50%] translate-x-[-50%]": true,
|
||||
"max-w-[calc(100dvw-30px)] max-h-[85dvh]": true,
|
||||
"bg-base-100 rounded overflow-y-auto p-0": true,
|
||||
"overflow-x-hidden whitespace-normal": true,
|
||||
|
||||
"w-[400px]": size == SizeSm,
|
||||
"w-[600px]": size == SizeMd,
|
||||
"w-[800px]": size == SizeLg,
|
||||
},
|
||||
|
||||
html.Div(
|
||||
components.Classes{
|
||||
"w-full sticky top-0 right-0 bg-base-100": true,
|
||||
"flex items-center justify-between": true,
|
||||
"border-b border-base-300 px-4 py-3": true,
|
||||
},
|
||||
|
||||
html.Div(
|
||||
gomponents.If(
|
||||
params.TitleNode != nil,
|
||||
params.TitleNode,
|
||||
),
|
||||
|
||||
gomponents.If(
|
||||
params.Title != "",
|
||||
html.Span(
|
||||
html.Class("text-xl font-bold desk:text-2xl"),
|
||||
gomponents.Text(params.Title),
|
||||
),
|
||||
),
|
||||
|
||||
gomponents.If(
|
||||
hasHTMXIndicator,
|
||||
html.Div(
|
||||
html.Class("inline-flex h-full items-center pl-2"),
|
||||
HxLoadingSm(false, params.HTMXIndicator),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
html.Button(
|
||||
html.Class("btn btn-circle btn-ghost btn-sm"),
|
||||
lucide.X(html.Class("size-6")),
|
||||
closerAttr,
|
||||
),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
html.Class("p-4"),
|
||||
gomponents.Group(params.Content),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return ModalResult{
|
||||
OpenerAttr: openerAttr,
|
||||
HTML: content,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lucide "github.com/eduardolat/gomponents-lucide"
|
||||
"github.com/maragudk/gomponents"
|
||||
"github.com/maragudk/gomponents/components"
|
||||
"github.com/maragudk/gomponents/html"
|
||||
)
|
||||
|
||||
func spinner(size size) gomponents.Node {
|
||||
return lucide.LoaderCircle(components.Classes{
|
||||
"animate-spin inline-block": true,
|
||||
"size-5": size == SizeSm,
|
||||
"size-8": size == SizeMd,
|
||||
"size-12": size == SizeLg,
|
||||
})
|
||||
}
|
||||
|
||||
func SpinnerSm() gomponents.Node {
|
||||
return spinner(SizeSm)
|
||||
}
|
||||
|
||||
func SpinnerMd() gomponents.Node {
|
||||
return spinner(SizeMd)
|
||||
}
|
||||
|
||||
func SpinnerLg() gomponents.Node {
|
||||
return spinner(SizeLg)
|
||||
}
|
||||
|
||||
func spinnerContainer(size size, height string) gomponents.Node {
|
||||
return html.Div(
|
||||
components.Classes{
|
||||
"flex justify-center": true,
|
||||
"items-center w-full": true,
|
||||
},
|
||||
html.Style(fmt.Sprintf("height: %s;", height)),
|
||||
spinner(size),
|
||||
)
|
||||
}
|
||||
|
||||
func SpinnerContainerSm(height ...string) gomponents.Node {
|
||||
pickedHeight := "300px"
|
||||
if len(height) > 0 {
|
||||
pickedHeight = height[0]
|
||||
}
|
||||
return spinnerContainer(SizeSm, pickedHeight)
|
||||
}
|
||||
|
||||
func SpinnerContainerMd(height ...string) gomponents.Node {
|
||||
pickedHeight := "300px"
|
||||
if len(height) > 0 {
|
||||
pickedHeight = height[0]
|
||||
}
|
||||
return spinnerContainer(SizeMd, pickedHeight)
|
||||
}
|
||||
|
||||
func SpinnerContainerLg(height ...string) gomponents.Node {
|
||||
pickedHeight := "300px"
|
||||
if len(height) > 0 {
|
||||
pickedHeight = height[0]
|
||||
}
|
||||
return spinnerContainer(SizeLg, pickedHeight)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"github.com/maragudk/gomponents"
|
||||
"github.com/maragudk/gomponents/html"
|
||||
)
|
||||
|
||||
func H1(children ...gomponents.Node) gomponents.Node {
|
||||
return html.H1(
|
||||
html.Class("text-2xl font-bold desk:text-4xl"),
|
||||
gomponents.Group(children),
|
||||
)
|
||||
}
|
||||
|
||||
func H2(children ...gomponents.Node) gomponents.Node {
|
||||
return html.H2(
|
||||
html.Class("text-xl font-bold desk:text-2xl"),
|
||||
gomponents.Group(children),
|
||||
)
|
||||
}
|
||||
|
||||
func H3(children ...gomponents.Node) gomponents.Node {
|
||||
return html.H3(
|
||||
html.Class("text-lg font-bold desk:text-xl"),
|
||||
gomponents.Group(children),
|
||||
)
|
||||
}
|
||||
|
||||
func H4(children ...gomponents.Node) gomponents.Node {
|
||||
return html.H4(
|
||||
html.Class("text-base font-bold desk:text-lg"),
|
||||
gomponents.Group(children),
|
||||
)
|
||||
}
|
||||
|
||||
func H5(children ...gomponents.Node) gomponents.Node {
|
||||
return html.H5(
|
||||
html.Class("text-sm font-bold desk:text-base"),
|
||||
gomponents.Group(children),
|
||||
)
|
||||
}
|
||||
|
||||
func H6(children ...gomponents.Node) gomponents.Node {
|
||||
return html.H6(
|
||||
html.Class("text-xs font-bold desk:text-sm"),
|
||||
gomponents.Group(children),
|
||||
)
|
||||
}
|
||||
|
||||
// H1Text is a convenience function to create an H1 element with a
|
||||
// simple text node as its child.
|
||||
func H1Text(text string) gomponents.Node {
|
||||
return H1(gomponents.Text(text))
|
||||
}
|
||||
|
||||
// H2Text is a convenience function to create an H2 element with a
|
||||
// simple text node as its child.
|
||||
func H2Text(text string) gomponents.Node {
|
||||
return H2(gomponents.Text(text))
|
||||
}
|
||||
|
||||
// H3Text is a convenience function to create an H3 element with a
|
||||
// simple text node as its child.
|
||||
func H3Text(text string) gomponents.Node {
|
||||
return H3(gomponents.Text(text))
|
||||
}
|
||||
|
||||
// H4Text is a convenience function to create an H4 element with a
|
||||
// simple text node as its child.
|
||||
func H4Text(text string) gomponents.Node {
|
||||
return H4(gomponents.Text(text))
|
||||
}
|
||||
|
||||
// H5Text is a convenience function to create an H5 element with a
|
||||
// simple text node as its child.
|
||||
func H5Text(text string) gomponents.Node {
|
||||
return H5(gomponents.Text(text))
|
||||
}
|
||||
|
||||
// H6Text is a convenience function to create an H6 element with a
|
||||
// simple text node as its child.
|
||||
func H6Text(text string) gomponents.Node {
|
||||
return H6(gomponents.Text(text))
|
||||
}
|
||||
|
||||
// PText is a convenience function to create a P element with a
|
||||
// simple text node as its child.
|
||||
func PText(text string) gomponents.Node {
|
||||
return html.P(gomponents.Text(text))
|
||||
}
|
||||
|
||||
// SpanText is a convenience function to create a Span element with a
|
||||
// simple text node as its child.
|
||||
func SpanText(text string) gomponents.Node {
|
||||
return html.Span(gomponents.Text(text))
|
||||
}
|
||||
Reference in New Issue
Block a user