replace node everywhere with renderable
This commit is contained in:
parent
de28a6a783
commit
fec5558f28
17 changed files with 145 additions and 188 deletions
2
h/app.go
2
h/app.go
|
|
@ -39,7 +39,7 @@ func (a App) start(app *fiber.App) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func HtmlView(c *fiber.Ctx, page *Page) error {
|
func HtmlView(c *fiber.Ctx, page *Page) error {
|
||||||
root := page.Root
|
root := page.Root.Render()
|
||||||
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
||||||
|
|
||||||
if GetApp().LiveReload && root.tag == "html" {
|
if GetApp().LiveReload && root.tag == "html" {
|
||||||
|
|
|
||||||
18
h/base.go
18
h/base.go
|
|
@ -15,23 +15,27 @@ type Partial struct {
|
||||||
Root *Node
|
Root *Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Partial) Render() *Node {
|
||||||
|
return p.Root
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Partial) ToNode() *Node {
|
func (p *Partial) ToNode() *Node {
|
||||||
return p.Root
|
return p.Root
|
||||||
}
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
Root *Node
|
Root Renderable
|
||||||
HttpMethod string
|
HttpMethod string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPage(root *Node) *Page {
|
func NewPage(root Renderable) *Page {
|
||||||
return &Page{
|
return &Page{
|
||||||
HttpMethod: http.MethodGet,
|
HttpMethod: http.MethodGet,
|
||||||
Root: root,
|
Root: root,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPageWithHttpMethod(httpMethod string, root *Node) *Page {
|
func NewPageWithHttpMethod(httpMethod string, root Renderable) *Page {
|
||||||
return &Page{
|
return &Page{
|
||||||
HttpMethod: httpMethod,
|
HttpMethod: httpMethod,
|
||||||
Root: root,
|
Root: root,
|
||||||
|
|
@ -42,16 +46,16 @@ func WrapPartial(ctx *fiber.Ctx, cb func(ctx *fiber.Ctx) *Partial) *Node {
|
||||||
return cb(ctx).Root
|
return cb(ctx).Root
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPartialWithHeaders(headers *Headers, root *Node) *Partial {
|
func NewPartialWithHeaders(headers *Headers, root Renderable) *Partial {
|
||||||
return &Partial{
|
return &Partial{
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
Root: root,
|
Root: root.Render(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPartial(root *Node) *Partial {
|
func NewPartial(root Renderable) *Partial {
|
||||||
return &Partial{
|
return &Partial{
|
||||||
Root: root,
|
Root: root.Render(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ func LiveReloadHandler(c *fiber.Ctx) error {
|
||||||
return c.SendString("")
|
return c.SendString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func LiveReload() *Node {
|
func LiveReload() Renderable {
|
||||||
return Div(Get("/livereload"), Trigger("every 200ms"))
|
return Div(Get("/livereload"), Trigger("every 200ms"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
51
h/render.go
51
h/render.go
|
|
@ -39,14 +39,15 @@ func (page Builder) renderNode(node *Node) {
|
||||||
node.attributes = map[string]string{}
|
node.attributes = map[string]string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
flatChildren := make([]*Node, 0)
|
flatChildren := make([]Renderable, 0)
|
||||||
for _, child := range node.children {
|
for _, child := range node.children {
|
||||||
|
c := child.Render()
|
||||||
flatChildren = append(flatChildren, child)
|
flatChildren = append(flatChildren, child)
|
||||||
if child.tag == FlagChildrenList {
|
if c.tag == FlagChildrenList {
|
||||||
for _, gc := range child.children {
|
for _, gc := range c.children {
|
||||||
flatChildren = append(flatChildren, gc)
|
flatChildren = append(flatChildren, gc)
|
||||||
}
|
}
|
||||||
child.tag = FlagSkip
|
c.tag = FlagSkip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,30 +57,32 @@ func (page Builder) renderNode(node *Node) {
|
||||||
|
|
||||||
for _, child := range node.children {
|
for _, child := range node.children {
|
||||||
|
|
||||||
|
c := child.Render()
|
||||||
if child == nil {
|
if child == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if child.tag == "class" {
|
if c.tag == "class" {
|
||||||
insertAttribute(node, "class", child.value)
|
insertAttribute(node, "class", c.value)
|
||||||
child.tag = FlagSkip
|
c.tag = FlagSkip
|
||||||
}
|
}
|
||||||
|
|
||||||
if child.tag == FlagAttributeList {
|
if c.tag == FlagAttributeList {
|
||||||
for _, gc := range child.children {
|
for _, gc := range c.children {
|
||||||
for key, value := range gc.attributes {
|
gcr := gc.Render()
|
||||||
|
for key, value := range gcr.attributes {
|
||||||
insertAttribute(node, key, value)
|
insertAttribute(node, key, value)
|
||||||
}
|
}
|
||||||
gc.tag = FlagSkip
|
gcr.tag = FlagSkip
|
||||||
}
|
}
|
||||||
child.tag = FlagSkip
|
c.tag = FlagSkip
|
||||||
}
|
}
|
||||||
|
|
||||||
if child.tag == "attribute" {
|
if c.tag == "attribute" {
|
||||||
for key, value := range child.attributes {
|
for key, value := range c.attributes {
|
||||||
insertAttribute(node, key, value)
|
insertAttribute(node, key, value)
|
||||||
}
|
}
|
||||||
child.tag = FlagSkip
|
c.tag = FlagSkip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,16 +111,18 @@ func (page Builder) renderNode(node *Node) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if child.tag == FlagText {
|
c := child.Render()
|
||||||
page.builder.WriteString(child.text)
|
|
||||||
|
if c.tag == FlagText {
|
||||||
|
page.builder.WriteString(c.text)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if child.tag == FlagRaw {
|
if c.tag == FlagRaw {
|
||||||
page.builder.WriteString(child.value)
|
page.builder.WriteString(c.value)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if child.tag != FlagSkip {
|
if c.tag != FlagSkip {
|
||||||
page.renderNode(child)
|
page.renderNode(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if node.tag != "" {
|
if node.tag != "" {
|
||||||
|
|
@ -125,12 +130,12 @@ func (page Builder) renderNode(node *Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Render(node *Node) string {
|
func Render(node Renderable) string {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
builder := strings.Builder{}
|
builder := strings.Builder{}
|
||||||
page := Builder{
|
page := Builder{
|
||||||
builder: &builder,
|
builder: &builder,
|
||||||
root: node,
|
root: node.Render(),
|
||||||
}
|
}
|
||||||
page.render()
|
page.render()
|
||||||
d := page.builder.String()
|
d := page.builder.String()
|
||||||
|
|
|
||||||
153
h/tag.go
153
h/tag.go
|
|
@ -13,29 +13,27 @@ type Node struct {
|
||||||
id string
|
id string
|
||||||
tag string
|
tag string
|
||||||
attributes map[string]string
|
attributes map[string]string
|
||||||
children []*Node
|
children []Renderable
|
||||||
text string
|
text string
|
||||||
value string
|
value string
|
||||||
changed bool
|
changed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action struct {
|
func (node *Node) Render() *Node {
|
||||||
Type string
|
return node
|
||||||
Target *Node
|
|
||||||
Value any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) AppendChild(child *Node) *Node {
|
func (node *Node) AppendChild(child Renderable) Renderable {
|
||||||
node.children = append(node.children, child)
|
node.children = append(node.children, child)
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *Node) SetChanged(changed bool) *Node {
|
func (node *Node) SetChanged(changed bool) Renderable {
|
||||||
node.changed = changed
|
node.changed = changed
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func Data(data map[string]any) *Node {
|
func Data(data map[string]any) Renderable {
|
||||||
serialized, err := json.Marshal(data)
|
serialized, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Empty()
|
return Empty()
|
||||||
|
|
@ -43,14 +41,14 @@ func Data(data map[string]any) *Node {
|
||||||
return Attribute("x-data", string(serialized))
|
return Attribute("x-data", string(serialized))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClassIf(condition bool, value string) *Node {
|
func ClassIf(condition bool, value string) Renderable {
|
||||||
if condition {
|
if condition {
|
||||||
return Class(value)
|
return Class(value)
|
||||||
}
|
}
|
||||||
return Empty()
|
return Empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Class(value ...string) *Node {
|
func Class(value ...string) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "class",
|
tag: "class",
|
||||||
value: MergeClasses(value...),
|
value: MergeClasses(value...),
|
||||||
|
|
@ -65,37 +63,37 @@ func MergeClasses(classes ...string) string {
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func Id(value string) *Node {
|
func Id(value string) Renderable {
|
||||||
if strings.HasPrefix(value, "#") {
|
if strings.HasPrefix(value, "#") {
|
||||||
value = value[1:]
|
value = value[1:]
|
||||||
}
|
}
|
||||||
return Attribute("id", value)
|
return Attribute("id", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Attributes(attrs map[string]string) *Node {
|
func Attributes(attrs map[string]string) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "attribute",
|
tag: "attribute",
|
||||||
attributes: attrs,
|
attributes: attrs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Attribute(key string, value string) *Node {
|
func Attribute(key string, value string) Renderable {
|
||||||
return Attributes(map[string]string{key: value})
|
return Attributes(map[string]string{key: value})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Disabled() *Node {
|
func Disabled() Renderable {
|
||||||
return Attribute("disabled", "")
|
return Attribute("disabled", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(path string) *Node {
|
func Get(path string) Renderable {
|
||||||
return Attribute("hx-get", path)
|
return Attribute("hx-get", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartial(partial func(ctx *fiber.Ctx) *Partial) *Node {
|
func GetPartial(partial func(ctx *fiber.Ctx) *Partial) Renderable {
|
||||||
return Get(GetPartialPath(partial))
|
return Get(GetPartialPath(partial))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartialWithQs(partial func(ctx *fiber.Ctx) *Partial, qs string) *Node {
|
func GetPartialWithQs(partial func(ctx *fiber.Ctx) *Partial, qs string) Renderable {
|
||||||
return Get(GetPartialPathWithQs(partial, qs))
|
return Get(GetPartialPathWithQs(partial, qs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,27 +105,27 @@ type ReloadParams struct {
|
||||||
Triggers []string
|
Triggers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ViewOnLoad(partial func(ctx *fiber.Ctx) *Partial) *Node {
|
func ViewOnLoad(partial func(ctx *fiber.Ctx) *Partial) Renderable {
|
||||||
return View(partial, ReloadParams{
|
return View(partial, ReloadParams{
|
||||||
Triggers: CreateTriggers("load"),
|
Triggers: CreateTriggers("load"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func View(partial func(ctx *fiber.Ctx) *Partial, params ReloadParams) *Node {
|
func View(partial func(ctx *fiber.Ctx) *Partial, params ReloadParams) Renderable {
|
||||||
return Div(Attributes(map[string]string{
|
return Div(Attributes(map[string]string{
|
||||||
"hx-get": GetPartialPath(partial),
|
"hx-get": GetPartialPath(partial),
|
||||||
"hx-trigger": strings.Join(params.Triggers, ", "),
|
"hx-trigger": strings.Join(params.Triggers, ", "),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ViewWithTriggers(partial func(ctx *fiber.Ctx) *Partial, triggers ...string) *Node {
|
func ViewWithTriggers(partial func(ctx *fiber.Ctx) *Partial, triggers ...string) Renderable {
|
||||||
return Div(Attributes(map[string]string{
|
return Div(Attributes(map[string]string{
|
||||||
"hx-get": GetPartialPath(partial),
|
"hx-get": GetPartialPath(partial),
|
||||||
"hx-trigger": strings.Join(triggers, ", "),
|
"hx-trigger": strings.Join(triggers, ", "),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWithQs(path string, qs map[string]string) *Node {
|
func GetWithQs(path string, qs map[string]string) Renderable {
|
||||||
u, err := url.Parse(path)
|
u, err := url.Parse(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Empty()
|
return Empty()
|
||||||
|
|
@ -144,104 +142,104 @@ func GetWithQs(path string, qs map[string]string) *Node {
|
||||||
return Get(u.String())
|
return Get(u.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func Post(url string) *Node {
|
func Post(url string) Renderable {
|
||||||
return Attribute("hx-post", url)
|
return Attribute("hx-post", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Trigger(trigger string) *Node {
|
func Trigger(trigger string) Renderable {
|
||||||
return Attribute("hx-trigger", trigger)
|
return Attribute("hx-trigger", trigger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Text(text string) *Node {
|
func Text(text string) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "text",
|
tag: "text",
|
||||||
text: text,
|
text: text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pf(format string, args ...interface{}) *Node {
|
func Pf(format string, args ...interface{}) Renderable {
|
||||||
return P(fmt.Sprintf(format, args...))
|
return P(fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Target(target string) *Node {
|
func Target(target string) Renderable {
|
||||||
return Attribute("hx-target", target)
|
return Attribute("hx-target", target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Name(name string) *Node {
|
func Name(name string) Renderable {
|
||||||
return Attribute("name", name)
|
return Attribute("name", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Confirm(message string) *Node {
|
func Confirm(message string) Renderable {
|
||||||
return Attribute("hx-confirm", message)
|
return Attribute("hx-confirm", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Href(path string) *Node {
|
func Href(path string) Renderable {
|
||||||
return Attribute("href", path)
|
return Attribute("href", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Type(name string) *Node {
|
func Type(name string) Renderable {
|
||||||
return Attribute("type", name)
|
return Attribute("type", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Placeholder(placeholder string) *Node {
|
func Placeholder(placeholder string) Renderable {
|
||||||
return Attribute("placeholder", placeholder)
|
return Attribute("placeholder", placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OutOfBandSwap(selector string) *Node {
|
func OutOfBandSwap(selector string) Renderable {
|
||||||
return Attribute("hx-swap-oob",
|
return Attribute("hx-swap-oob",
|
||||||
Ternary(selector == "", "true", selector))
|
Ternary(selector == "", "true", selector))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Click(value string) *Node {
|
func Click(value string) Renderable {
|
||||||
return Attribute("onclick", value)
|
return Attribute("onclick", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tag(tag string, children ...*Node) *Node {
|
func Tag(tag string, children ...Renderable) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: tag,
|
tag: tag,
|
||||||
children: children,
|
children: children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Html(children ...*Node) *Node {
|
func Html(children ...Renderable) Renderable {
|
||||||
return Tag("html", children...)
|
return Tag("html", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Head(children ...*Node) *Node {
|
func Head(children ...Renderable) Renderable {
|
||||||
return Tag("head", children...)
|
return Tag("head", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Body(children ...*Node) *Node {
|
func Body(children ...Renderable) Renderable {
|
||||||
return Tag("body", children...)
|
return Tag("body", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Script(url string) *Node {
|
func Script(url string) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "script",
|
tag: "script",
|
||||||
attributes: map[string]string{
|
attributes: map[string]string{
|
||||||
"src": url,
|
"src": url,
|
||||||
},
|
},
|
||||||
children: make([]*Node, 0),
|
children: make([]Renderable, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Raw(text string) *Node {
|
func Raw(text string) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "raw",
|
tag: "raw",
|
||||||
children: make([]*Node, 0),
|
children: make([]Renderable, 0),
|
||||||
value: text,
|
value: text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RawScript(text string) *Node {
|
func RawScript(text string) Renderable {
|
||||||
return Raw("<script>" + text + "</script>")
|
return Raw("<script>" + text + "</script>")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Div(children ...*Node) *Node {
|
func Div(children ...Renderable) Renderable {
|
||||||
return Tag("div", children...)
|
return Tag("div", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Input(inputType string, children ...*Node) *Node {
|
func Input(inputType string, children ...Renderable) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "input",
|
tag: "input",
|
||||||
attributes: map[string]string{
|
attributes: map[string]string{
|
||||||
|
|
@ -251,10 +249,10 @@ func Input(inputType string, children ...*Node) *Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func List[T any](items []T, mapper func(item T, index int) *Node) *Node {
|
func List[T any](items []T, mapper func(item T, index int) Renderable) Renderable {
|
||||||
node := &Node{
|
node := &Node{
|
||||||
tag: "",
|
tag: "",
|
||||||
children: make([]*Node, len(items)),
|
children: make([]Renderable, len(items)),
|
||||||
}
|
}
|
||||||
for index, value := range items {
|
for index, value := range items {
|
||||||
node.children[index] = mapper(value, index)
|
node.children[index] = mapper(value, index)
|
||||||
|
|
@ -262,35 +260,35 @@ func List[T any](items []T, mapper func(item T, index int) *Node) *Node {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fragment(children ...*Node) *Node {
|
func Fragment(children ...Renderable) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "",
|
tag: "",
|
||||||
children: children,
|
children: children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AttributeList(children ...*Node) *Node {
|
func AttributeList(children ...Renderable) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: FlagAttributeList,
|
tag: FlagAttributeList,
|
||||||
children: children,
|
children: children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendChildren(node *Node, children ...*Node) *Node {
|
func AppendChildren(node *Node, children ...Renderable) Renderable {
|
||||||
node.children = append(node.children, children...)
|
node.children = append(node.children, children...)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Button(children ...*Node) *Node {
|
func Button(children ...Renderable) Renderable {
|
||||||
return Tag("button", children...)
|
return Tag("button", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Indicator(tag string) *Node {
|
func Indicator(tag string) Renderable {
|
||||||
return Attribute("hx-indicator", tag)
|
return Attribute("hx-indicator", tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func P(text string, children ...*Node) *Node {
|
func P(text string, children ...Renderable) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "p",
|
tag: "p",
|
||||||
children: children,
|
children: children,
|
||||||
|
|
@ -298,11 +296,11 @@ func P(text string, children ...*Node) *Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Form(children ...*Node) *Node {
|
func Form(children ...Renderable) Renderable {
|
||||||
return Tag("form", children...)
|
return Tag("form", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func A(text string, children ...*Node) *Node {
|
func A(text string, children ...Renderable) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "a",
|
tag: "a",
|
||||||
children: children,
|
children: children,
|
||||||
|
|
@ -310,42 +308,42 @@ func A(text string, children ...*Node) *Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Nav(children ...*Node) *Node {
|
func Nav(children ...Renderable) Renderable {
|
||||||
return Tag("nav", children...)
|
return Tag("nav", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Empty() *Node {
|
func Empty() Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: "",
|
tag: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeforeRequestSetHtml(children ...*Node) *Node {
|
func BeforeRequestSetHtml(children ...Renderable) Renderable {
|
||||||
serialized := Render(Fragment(children...))
|
serialized := Render(Fragment(children...))
|
||||||
return Attribute("hx-on::before-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
|
return Attribute("hx-on::before-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeforeRequestSetAttribute(key string, value string) *Node {
|
func BeforeRequestSetAttribute(key string, value string) Renderable {
|
||||||
return Attribute("hx-on::before-request", `this.setAttribute('`+key+`', '`+value+`')`)
|
return Attribute("hx-on::before-request", `this.setAttribute('`+key+`', '`+value+`')`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeforeRequestSetText(text string) *Node {
|
func BeforeRequestSetText(text string) Renderable {
|
||||||
return Attribute("hx-on::before-request", `this.innerText = '`+text+`'`)
|
return Attribute("hx-on::before-request", `this.innerText = '`+text+`'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AfterRequestRemoveAttribute(key string, value string) *Node {
|
func AfterRequestRemoveAttribute(key string, value string) Renderable {
|
||||||
return Attribute("hx-on::after-request", `this.removeAttribute('`+key+`')`)
|
return Attribute("hx-on::after-request", `this.removeAttribute('`+key+`')`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IfQueryParam(key string, node *Node) *Node {
|
func IfQueryParam(key string, node *Node) Renderable {
|
||||||
return Fragment(Attribute("hx-if-qp:"+key, "true"), node)
|
return Fragment(Attribute("hx-if-qp:"+key, "true"), node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Hidden() *Node {
|
func Hidden() Renderable {
|
||||||
return Attribute("style", "display:none")
|
return Attribute("style", "display:none")
|
||||||
}
|
}
|
||||||
|
|
||||||
func MatchQueryParam(defaultValue string, active string, m map[string]*Node) *Node {
|
func MatchQueryParam(defaultValue string, active string, m map[string]*Node) Renderable {
|
||||||
|
|
||||||
rendered := make(map[string]string)
|
rendered := make(map[string]string)
|
||||||
for s, node := range m {
|
for s, node := range m {
|
||||||
|
|
@ -360,29 +358,29 @@ func MatchQueryParam(defaultValue string, active string, m map[string]*Node) *No
|
||||||
)
|
)
|
||||||
|
|
||||||
for s, node := range rendered {
|
for s, node := range rendered {
|
||||||
root = AppendChildren(root, Attribute("hx-match-qp-mapping:"+s, ``+html.EscapeString(node)+``))
|
root = AppendChildren(root.Render(), Attribute("hx-match-qp-mapping:"+s, ``+html.EscapeString(node)+``))
|
||||||
}
|
}
|
||||||
|
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
func AfterRequestSetHtml(children ...*Node) *Node {
|
func AfterRequestSetHtml(children ...Renderable) Renderable {
|
||||||
serialized := Render(Fragment(children...))
|
serialized := Render(Fragment(children...))
|
||||||
return Attribute("hx-on::after-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
|
return Attribute("hx-on::after-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Children(children []*Node) *Node {
|
func Children(children []Renderable) Renderable {
|
||||||
return &Node{
|
return &Node{
|
||||||
tag: FlagChildrenList,
|
tag: FlagChildrenList,
|
||||||
children: children,
|
children: children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Label(text string) *Node {
|
func Label(text string) Renderable {
|
||||||
return Tag("label", Text(text))
|
return Tag("label", Text(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
func If(condition bool, node *Node) *Node {
|
func If(condition bool, node Renderable) Renderable {
|
||||||
if condition {
|
if condition {
|
||||||
return node
|
return node
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -390,7 +388,7 @@ func If(condition bool, node *Node) *Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IfElse(condition bool, node *Node, node2 *Node) *Node {
|
func IfElse(condition bool, node Renderable, node2 Renderable) Renderable {
|
||||||
if condition {
|
if condition {
|
||||||
return node
|
return node
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -398,7 +396,7 @@ func IfElse(condition bool, node *Node, node2 *Node) *Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IfElseLazy(condition bool, cb1 func() *Node, cb2 func() *Node) *Node {
|
func IfElseLazy(condition bool, cb1 func() Renderable, cb2 func() Renderable) Renderable {
|
||||||
if condition {
|
if condition {
|
||||||
return cb1()
|
return cb1()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -406,7 +404,7 @@ func IfElseLazy(condition bool, cb1 func() *Node, cb2 func() *Node) *Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IfHtmxRequest(ctx *fiber.Ctx, node *Node) *Node {
|
func IfHtmxRequest(ctx *fiber.Ctx, node Renderable) Renderable {
|
||||||
if ctx.Get("HX-Request") != "" {
|
if ctx.Get("HX-Request") != "" {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
@ -425,25 +423,26 @@ func NewSwap(selector string, content *Node) SwapArg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Swap(ctx *fiber.Ctx, content *Node) *Node {
|
func Swap(ctx *fiber.Ctx, content Renderable) Renderable {
|
||||||
return SwapWithSelector(ctx, "", content)
|
return SwapWithSelector(ctx, "", content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SwapWithSelector(ctx *fiber.Ctx, selector string, content *Node) *Node {
|
func SwapWithSelector(ctx *fiber.Ctx, selector string, content Renderable) Renderable {
|
||||||
if ctx == nil || ctx.Get("HX-Request") == "" {
|
if ctx == nil || ctx.Get("HX-Request") == "" {
|
||||||
return Empty()
|
return Empty()
|
||||||
}
|
}
|
||||||
return content.AppendChild(OutOfBandSwap(selector))
|
c := content.Render()
|
||||||
|
return c.AppendChild(OutOfBandSwap(selector))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SwapMany(ctx *fiber.Ctx, args ...SwapArg) *Node {
|
func SwapMany(ctx *fiber.Ctx, args ...SwapArg) Renderable {
|
||||||
if ctx.Get("HX-Request") == "" {
|
if ctx.Get("HX-Request") == "" {
|
||||||
return Empty()
|
return Empty()
|
||||||
}
|
}
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
arg.Content.AppendChild(OutOfBandSwap(arg.Selector))
|
arg.Content.AppendChild(OutOfBandSwap(arg.Selector))
|
||||||
}
|
}
|
||||||
return Fragment(Map(args, func(arg SwapArg) *Node {
|
return Fragment(Map(args, func(arg SwapArg) Renderable {
|
||||||
return arg.Content
|
return arg.Content
|
||||||
})...)
|
})...)
|
||||||
}
|
}
|
||||||
|
|
@ -456,7 +455,7 @@ type OnRequestSwapArgs struct {
|
||||||
AfterRequest *Node
|
AfterRequest *Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnRequestSwap(args OnRequestSwapArgs) *Node {
|
func OnRequestSwap(args OnRequestSwapArgs) Renderable {
|
||||||
return Div(args.Default,
|
return Div(args.Default,
|
||||||
BeforeRequestSetHtml(args.BeforeRequest),
|
BeforeRequestSetHtml(args.BeforeRequest),
|
||||||
AfterRequestSetHtml(args.AfterRequest),
|
AfterRequestSetHtml(args.AfterRequest),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Renderable interface {
|
||||||
|
Render() *Node
|
||||||
|
}
|
||||||
|
|
||||||
func Ternary[T any](value bool, a T, b T) T {
|
func Ternary[T any](value bool, a T, b T) T {
|
||||||
if value {
|
if value {
|
||||||
return a
|
return a
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StoryList() *h.Node {
|
func StoryList() h.Renderable {
|
||||||
|
|
||||||
posts, _ := database.GetOrSet[[]Post]("posts", func() []Post {
|
posts, _ := database.GetOrSet[[]Post]("posts", func() []Post {
|
||||||
p, _ := List()
|
p, _ := List()
|
||||||
|
|
@ -21,13 +21,13 @@ func StoryList() *h.Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.Fragment(
|
return h.Fragment(
|
||||||
h.Div(h.List(*posts, func(item Post, index int) *h.Node {
|
h.Div(h.List(*posts, func(item Post, index int) h.Renderable {
|
||||||
return StoryCard(item)
|
return StoryCard(item)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoryCard(post Post) *h.Node {
|
func StoryCard(post Post) h.Renderable {
|
||||||
url := fmt.Sprintf("/news/%d", post.Id)
|
url := fmt.Sprintf("/news/%d", post.Id)
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("items-center bg-indigo-200 p-4 rounded"),
|
h.Class("items-center bg-indigo-200 p-4 rounded"),
|
||||||
|
|
@ -35,7 +35,7 @@ func StoryCard(post Post) *h.Node {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoryFull(id string) *h.Node {
|
func StoryFull(id string) h.Renderable {
|
||||||
post, err := Get(id)
|
post, err := Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return h.P(err.Error())
|
return h.P(err.Error())
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"mhtml/partials/sheet"
|
"mhtml/partials/sheet"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RootPage(children ...*h.Node) *h.Node {
|
func RootPage(children ...h.Renderable) h.Renderable {
|
||||||
return h.Html(
|
return h.Html(
|
||||||
h.Head(
|
h.Head(
|
||||||
h.Script("https://cdn.tailwindcss.com"),
|
h.Script("https://cdn.tailwindcss.com"),
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ func ListPage(ctx *fiber.Ctx) *h.Page {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(ctx *fiber.Ctx) *h.Node {
|
func list(ctx *fiber.Ctx) h.Renderable {
|
||||||
return h.Fragment(
|
return h.Fragment(
|
||||||
h.ViewOnLoad(partials.NewsSheet),
|
h.ViewOnLoad(partials.NewsSheet),
|
||||||
h.Div(
|
h.Div(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"mhtml/ui"
|
"mhtml/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func OpenSheetButton(open bool, children ...*h.Node) *h.Node {
|
func OpenSheetButton(open bool, children ...h.Renderable) h.Renderable {
|
||||||
if open {
|
if open {
|
||||||
return ui.PrimaryButton(ui.ButtonProps{
|
return ui.PrimaryButton(ui.ButtonProps{
|
||||||
Id: "open-sheet",
|
Id: "open-sheet",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ type Link struct {
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NavBar() *h.Node {
|
func NavBar() h.Renderable {
|
||||||
|
|
||||||
links := []Link{
|
links := []Link{
|
||||||
{"Home", "/"},
|
{"Home", "/"},
|
||||||
|
|
@ -17,7 +17,7 @@ func NavBar() *h.Node {
|
||||||
|
|
||||||
return h.Nav(h.Class("flex gap-4 items-center p-4 text-slate-600"),
|
return h.Nav(h.Class("flex gap-4 items-center p-4 text-slate-600"),
|
||||||
h.Children(
|
h.Children(
|
||||||
h.Map(links, func(link Link) *h.Node {
|
h.Map(links, func(link Link) h.Renderable {
|
||||||
return h.A(link.Name, h.Href(link.Path), h.Class("cursor-pointer hover:text-blue-400"))
|
return h.A(link.Name, h.Href(link.Path), h.Class("cursor-pointer hover:text-blue-400"))
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,15 @@ func NewsSheetOpenCount(ctx *fiber.Ctx) *h.Partial {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SheetWrapper(children ...*h.Node) *h.Node {
|
func SheetWrapper(children ...h.Renderable) h.Renderable {
|
||||||
return h.Div(h.Id("sheet-partial"), h.Fragment(children...))
|
return h.Div(h.Id("sheet-partial"), h.Fragment(children...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func SheetClosed() *h.Node {
|
func SheetClosed() h.Renderable {
|
||||||
return h.Div()
|
return h.Div()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SheetOpen() *h.Node {
|
func SheetOpen() h.Renderable {
|
||||||
return h.Fragment(h.Div(
|
return h.Fragment(h.Div(
|
||||||
h.Class(`fixed top-0 right-0 h-full w-96 bg-gray-100 shadow-lg z-50`),
|
h.Class(`fixed top-0 right-0 h-full w-96 bg-gray-100 shadow-lg z-50`),
|
||||||
h.Div(
|
h.Div(
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func AddPatientSheet(ctx *fiber.Ctx) *h.Partial {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPatientForm() *h.Node {
|
func addPatientForm() h.Renderable {
|
||||||
return h.Form(
|
return h.Form(
|
||||||
h.Post(h.GetPartialPath(Create)),
|
h.Post(h.GetPartialPath(Create)),
|
||||||
h.Class("flex flex-col gap-2"),
|
h.Class("flex flex-col gap-2"),
|
||||||
|
|
@ -84,7 +84,7 @@ func addPatientForm() *h.Node {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Row(patient *Patient, index int) *h.Node {
|
func Row(patient *Patient, index int) h.Renderable {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("flex flex-col gap-2 rounded p-4", h.Ternary(index%2 == 0, "bg-red-100", "")),
|
h.Class("flex flex-col gap-2 rounded p-4", h.Ternary(index%2 == 0, "bg-red-100", "")),
|
||||||
h.Pf("Name: %s", patient.Name),
|
h.Pf("Name: %s", patient.Name),
|
||||||
|
|
@ -92,7 +92,7 @@ func Row(patient *Patient, index int) *h.Node {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddPatientButton() *h.Node {
|
func AddPatientButton() h.Renderable {
|
||||||
return ui.Button(ui.ButtonProps{
|
return ui.Button(ui.ButtonProps{
|
||||||
Id: "add-patient",
|
Id: "add-patient",
|
||||||
Text: "Add Patient",
|
Text: "Add Patient",
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ import (
|
||||||
|
|
||||||
type Props struct {
|
type Props struct {
|
||||||
ClassName string
|
ClassName string
|
||||||
Root *h.Node
|
Root h.Renderable
|
||||||
}
|
}
|
||||||
|
|
||||||
var Id = "#active-modal"
|
var Id = "#active-modal"
|
||||||
|
|
||||||
func Opened(props Props) *h.Node {
|
func Opened(props Props) h.Renderable {
|
||||||
return h.Fragment(h.Div(
|
return h.Fragment(h.Div(
|
||||||
h.Class(`fixed top-0 right-0 h-full shadow-lg z-50`,
|
h.Class(`fixed top-0 right-0 h-full shadow-lg z-50`,
|
||||||
h.Ternary(props.ClassName != "", props.ClassName, "w-96 bg-gray-100")),
|
h.Ternary(props.ClassName != "", props.ClassName, "w-96 bg-gray-100")),
|
||||||
|
|
@ -22,7 +22,7 @@ func Opened(props Props) *h.Node {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Closed() *h.Node {
|
func Closed() h.Renderable {
|
||||||
return h.Div(h.Id(Id))
|
return h.Div(h.Id(Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ func Close(ctx *fiber.Ctx) *h.Partial {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeButton() *h.Node {
|
func closeButton() h.Renderable {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("absolute top-0 right-0 p-3"),
|
h.Class("absolute top-0 right-0 p-3"),
|
||||||
h.Button(
|
h.Button(
|
||||||
|
|
|
||||||
55
sandbox.go
55
sandbox.go
|
|
@ -1,55 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "mhtml/h"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
h.Div(
|
|
||||||
h.Id("sandbox"),
|
|
||||||
h.Button(
|
|
||||||
h.Id("btn"),
|
|
||||||
h.Class("bg-blue-500 text-white p-2"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
h.Html(
|
|
||||||
h.Head(),
|
|
||||||
h.Body(
|
|
||||||
h.Nav(
|
|
||||||
h.Class("flex gap-4 items-center p-4 text-slate-600 "),
|
|
||||||
h.A(h.Href("/"), h.Class("cursor-pointer hover:text-blue-400 ")),
|
|
||||||
h.A(h.Class("cursor-pointer hover:text-blue-400 "),
|
|
||||||
h.Href("/news")), h.A(
|
|
||||||
h.Href("/patients"),
|
|
||||||
h.Class("cursor-pointer hover:text-blue-400 "))),
|
|
||||||
h.Div(
|
|
||||||
h.Id("active-modal")),
|
|
||||||
h.Div(h.Class("flex flex-col gap-2 bg-white h-full "),
|
|
||||||
h.Div(h.Class("flex flex-col p-4 w-full "),
|
|
||||||
h.Div(h.Div(h.Class("flex justify-between items-center "),
|
|
||||||
h.P(
|
|
||||||
h.Class("text-lg font-bold ")),
|
|
||||||
h.Button(h.HxTarget("#active-modal"),
|
|
||||||
h.Type("button"),
|
|
||||||
h.Id("add-patient"),
|
|
||||||
h.Class("flex gap-1 items-center border p-4 rounded cursor-hover bg-blue-700 text-white rounded p-2 h-12 "),
|
|
||||||
h.HxGet("mhtml/partials/patient.AddPatientSheet"))),
|
|
||||||
h.Div(h.HxGet("mhtml/partials/patient.List"),
|
|
||||||
h.HxTrigger("load, patient-added from:body"),
|
|
||||||
h.Class(""), h.Div(h.Class("mt-8"),
|
|
||||||
h.Id("patient-list"),
|
|
||||||
h.Div(h.Class("flex flex-col gap-2 rounded p-4 bg-red-100 "),
|
|
||||||
h.P(),
|
|
||||||
h.P()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h.Div(
|
|
||||||
h.HxGet("/livereload"),
|
|
||||||
h.HxTrigger("every 200ms"),
|
|
||||||
h.Class(""),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -12,20 +12,20 @@ type ButtonProps struct {
|
||||||
Trigger string
|
Trigger string
|
||||||
Get string
|
Get string
|
||||||
Class string
|
Class string
|
||||||
Children []*h.Node
|
Children []h.Renderable
|
||||||
}
|
}
|
||||||
|
|
||||||
func PrimaryButton(props ButtonProps) *h.Node {
|
func PrimaryButton(props ButtonProps) h.Renderable {
|
||||||
props.Class = h.MergeClasses(props.Class, "border-blue-700 bg-blue-700 text-white")
|
props.Class = h.MergeClasses(props.Class, "border-blue-700 bg-blue-700 text-white")
|
||||||
return Button(props)
|
return Button(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SecondaryButton(props ButtonProps) *h.Node {
|
func SecondaryButton(props ButtonProps) h.Renderable {
|
||||||
props.Class = h.MergeClasses(props.Class, "border-gray-700 bg-gray-700 text-white")
|
props.Class = h.MergeClasses(props.Class, "border-gray-700 bg-gray-700 text-white")
|
||||||
return Button(props)
|
return Button(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Button(props ButtonProps) *h.Node {
|
func Button(props ButtonProps) h.Renderable {
|
||||||
|
|
||||||
text := h.Text(props.Text)
|
text := h.Text(props.Text)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ type InputProps struct {
|
||||||
DefaultValue string
|
DefaultValue string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Input(props InputProps) *h.Node {
|
func Input(props InputProps) h.Renderable {
|
||||||
input := h.Input(
|
input := h.Input(
|
||||||
props.Type,
|
props.Type,
|
||||||
h.Class("border p-2 rounded"),
|
h.Class("border p-2 rounded"),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue