wip
This commit is contained in:
99
web/components/ui/button.go
Normal file
99
web/components/ui/button.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/pkg/utils"
|
||||
)
|
||||
|
||||
type ButtonVariant string
|
||||
|
||||
const (
|
||||
ButtonVariantPrimary ButtonVariant = "primary"
|
||||
ButtonVariantSecondary ButtonVariant = "secondary"
|
||||
ButtonVariantGhost ButtonVariant = "ghost"
|
||||
)
|
||||
|
||||
type buttonAs int
|
||||
|
||||
const (
|
||||
buttonAsLink buttonAs = iota
|
||||
buttonAsForm
|
||||
buttonAsSpan
|
||||
)
|
||||
|
||||
type ButtonConfig struct {
|
||||
Variant ButtonVariant
|
||||
Disabled bool
|
||||
|
||||
as buttonAs
|
||||
value string
|
||||
}
|
||||
|
||||
// LinkButton creates a button that links to a url. The default variant is ButtonVariantPrimary.
|
||||
func LinkButton(content g.Node, url string, cfg ...ButtonConfig) g.Node {
|
||||
config := buildButtonConfig(cfg, buttonAsLink, url)
|
||||
return button(content, config)
|
||||
}
|
||||
|
||||
// FormButton creates a button that is a form. The default variant is ButtonVariantPrimary.
|
||||
func FormButton(content g.Node, formName string, cfg ...ButtonConfig) g.Node {
|
||||
config := buildButtonConfig(cfg, buttonAsForm, formName)
|
||||
return button(content, config)
|
||||
}
|
||||
|
||||
// SpanButton creates a button that has no target (i.e. span). The default variant is ButtonVariantPrimary.
|
||||
func SpanButton(content g.Node, cfg ...ButtonConfig) g.Node {
|
||||
config := buildButtonConfig(cfg, buttonAsSpan, "")
|
||||
return button(content, config)
|
||||
}
|
||||
|
||||
func button(content g.Node, config ButtonConfig) g.Node {
|
||||
classes := config.getClasses()
|
||||
if config.as == buttonAsSpan || config.Disabled {
|
||||
return h.Span(content, h.Class(classes))
|
||||
} else if config.as == buttonAsLink {
|
||||
return h.A(h.Class(classes), h.Href(config.value), content)
|
||||
}
|
||||
|
||||
return h.Button(
|
||||
content,
|
||||
h.Type("submit"),
|
||||
h.Class(classes),
|
||||
g.If(config.value != "", h.FormAttr(config.value)),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *ButtonConfig) getClasses() string {
|
||||
baseClass := "transition duration-100 ease-in font-medium text-center inline-block"
|
||||
|
||||
var variantClass string
|
||||
switch c.Variant {
|
||||
case ButtonVariantPrimary:
|
||||
variantClass = "h-full w-full px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||
case ButtonVariantSecondary:
|
||||
variantClass = "h-full w-full px-2 py-1 text-white bg-black shadow-md hover:text-black hover:bg-white"
|
||||
case ButtonVariantGhost:
|
||||
variantClass = "text-gray-500 hover:text-gray-800 dark:hover:text-gray-100"
|
||||
}
|
||||
|
||||
classes := baseClass + " " + variantClass
|
||||
|
||||
if c.Disabled {
|
||||
classes += " opacity-40 pointer-events-none"
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
func buildButtonConfig(cfg []ButtonConfig, as buttonAs, val string) ButtonConfig {
|
||||
c, found := sliceutils.First(cfg)
|
||||
if !found {
|
||||
c = ButtonConfig{Variant: ButtonVariantPrimary}
|
||||
}
|
||||
c.Variant = utils.FirstNonZero(c.Variant, ButtonVariantPrimary)
|
||||
c.as = as
|
||||
c.value = val
|
||||
return c
|
||||
}
|
||||
24
web/components/ui/kv.go
Normal file
24
web/components/ui/kv.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
// KeyValue is a basic vertical key/value pair component
|
||||
func KeyValue(key, val g.Node) g.Node {
|
||||
return h.Div(
|
||||
h.Class("flex flex-col"),
|
||||
h.Div(h.Class("text-gray-500"), key),
|
||||
h.Div(h.Class("font-medium text-black dark:text-white"), val),
|
||||
)
|
||||
}
|
||||
|
||||
// HKeyValue is a basic horizontal key/value pair component
|
||||
func HKeyValue(key, val g.Node) g.Node {
|
||||
return h.Div(
|
||||
h.Class("flex gap-2"),
|
||||
h.Div(h.Class("text-gray-500"), key),
|
||||
h.Div(h.Class("font-medium text-black dark:text-white"), val),
|
||||
)
|
||||
}
|
||||
99
web/components/ui/popover.go
Normal file
99
web/components/ui/popover.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/pkg/ptr"
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/pkg/utils"
|
||||
)
|
||||
|
||||
type PopoverPosition string
|
||||
|
||||
const (
|
||||
// ---- Cornered ----
|
||||
|
||||
// PopoverTopLeft PopoverPosition = "left-0 top-0 origin-bottom-right -translate-x-full -translate-y-full"
|
||||
// PopoverTopRight PopoverPosition = "right-0 top-0 origin-bottom-left translate-x-full -translate-y-full"
|
||||
// PopoverBottomLeft PopoverPosition = "left-0 bottom-0 origin-top-right -translate-x-full translate-y-full"
|
||||
// PopoverBottomRight PopoverPosition = "right-0 bottom-0 origin-top-left translate-x-full translate-y-full"
|
||||
|
||||
// ---- Flush ----
|
||||
|
||||
PopoverTopLeft PopoverPosition = "right-0 -top-1 origin-bottom-right -translate-y-full"
|
||||
PopoverTopRight PopoverPosition = "left-0 -top-1 origin-bottom-left -translate-y-full"
|
||||
PopoverBottomLeft PopoverPosition = "right-0 -bottom-1 origin-top-right translate-y-full"
|
||||
PopoverBottomRight PopoverPosition = "left-0 -bottom-1 origin-top-left translate-y-full"
|
||||
|
||||
// ---- Centered ----
|
||||
|
||||
PopoverTopCenter PopoverPosition = "left-1/2 top-0 origin-bottom -translate-x-1/2 -translate-y-full"
|
||||
PopoverBottomCenter PopoverPosition = "left-1/2 bottom-0 origin-top -translate-x-1/2 translate-y-full"
|
||||
PopoverLeftCenter PopoverPosition = "left-0 top-1/2 origin-right -translate-x-full -translate-y-1/2"
|
||||
PopoverRightCenter PopoverPosition = "right-0 top-1/2 origin-left translate-x-full -translate-y-1/2"
|
||||
PopoverCenter PopoverPosition = "left-1/2 top-1/2 origin-center -translate-x-1/2 -translate-y-1/2"
|
||||
)
|
||||
|
||||
type PopoverConfig struct {
|
||||
Position PopoverPosition
|
||||
Classes string
|
||||
Dim *bool
|
||||
}
|
||||
|
||||
// AnchoredPopover creates a popover with content anchored to the anchor node.
|
||||
// The default position is PopoverBottomRight.
|
||||
func AnchoredPopover(anchor, content g.Node, cfg ...PopoverConfig) g.Node {
|
||||
// Get Popover Config
|
||||
c, _ := sliceutils.First(cfg)
|
||||
c.Position = utils.FirstNonZero(c.Position, PopoverBottomRight)
|
||||
if c.Dim == nil {
|
||||
c.Dim = ptr.Of(false)
|
||||
}
|
||||
|
||||
popoverID := uuid.NewString()
|
||||
return h.Div(
|
||||
h.Class("relative"),
|
||||
h.Label(
|
||||
h.Class("cursor-pointer"),
|
||||
h.For(popoverID),
|
||||
anchor,
|
||||
),
|
||||
h.Input(
|
||||
h.ID(popoverID),
|
||||
h.Class("hidden css-button"),
|
||||
h.Type("checkbox"),
|
||||
),
|
||||
Popover(content, c),
|
||||
)
|
||||
}
|
||||
|
||||
func Popover(content g.Node, cfg ...PopoverConfig) g.Node {
|
||||
// Get Popover Config
|
||||
c, _ := sliceutils.First(cfg)
|
||||
c.Position = utils.FirstNonZero(c.Position, PopoverCenter)
|
||||
if c.Dim == nil {
|
||||
c.Dim = ptr.Of(true)
|
||||
}
|
||||
|
||||
wrappedContent := h.Div(h.Class(c.getClasses()), content)
|
||||
if !ptr.Deref(c.Dim) {
|
||||
return wrappedContent
|
||||
}
|
||||
|
||||
return h.Div(
|
||||
h.Div(h.Class("fixed top-0 left-0 bg-black z-40 opacity-50 w-screen h-screen")),
|
||||
wrappedContent,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *PopoverConfig) getClasses() string {
|
||||
return strings.Join([]string{
|
||||
"absolute z-50 p-2 transition-all duration-200 rounded shadow-lg",
|
||||
"bg-gray-200 dark:bg-gray-600 shadow-gray-500 dark:shadow-gray-900",
|
||||
c.Classes,
|
||||
string(c.Position),
|
||||
}, " ")
|
||||
}
|
||||
64
web/components/ui/table.go
Normal file
64
web/components/ui/table.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
type TableRow map[string]TableCell
|
||||
|
||||
type TableCell struct {
|
||||
String string
|
||||
Value g.Node
|
||||
}
|
||||
|
||||
type TableConfig struct {
|
||||
Columns []string
|
||||
Rows []TableRow
|
||||
}
|
||||
|
||||
func Table(cfg TableConfig) g.Node {
|
||||
return h.Table(
|
||||
h.Class("min-w-full leading-normal bg-white dark:bg-gray-700 text-sm"),
|
||||
h.THead(
|
||||
h.Class("text-gray-800 dark:text-gray-400"),
|
||||
h.Tr(
|
||||
g.Map(cfg.Columns, func(col string) g.Node {
|
||||
return h.Th(
|
||||
h.Class("p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"),
|
||||
g.Text(col),
|
||||
)
|
||||
})...,
|
||||
),
|
||||
),
|
||||
h.TBody(
|
||||
h.Class("text-black dark:text-white"),
|
||||
g.If(len(cfg.Rows) == 0,
|
||||
h.Tr(
|
||||
h.Td(
|
||||
h.Class("text-center p-3"),
|
||||
g.Attr("colspan", fmt.Sprintf("%d", len(cfg.Columns))),
|
||||
g.Text("No Results"),
|
||||
),
|
||||
),
|
||||
),
|
||||
g.Map(cfg.Rows, func(row TableRow) g.Node {
|
||||
return h.Tr(
|
||||
g.Map(cfg.Columns, func(col string) g.Node {
|
||||
cell, ok := row[col]
|
||||
content := cell.Value
|
||||
if !ok || content == nil {
|
||||
content = g.Text(cell.String)
|
||||
}
|
||||
return h.Td(
|
||||
h.Class("p-3 border-b border-gray-200"),
|
||||
content,
|
||||
)
|
||||
})...,
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user