diff --git a/go.mod b/go.mod
index dd36aa8..eb0dfc7 100644
--- a/go.mod
+++ b/go.mod
@@ -19,6 +19,7 @@ require (
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
+ github.com/haatos/goshipit v0.0.0-20260102021700-fcb988ab74c5 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -44,4 +45,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)
-tool github.com/a-h/templ/cmd/templ
+tool (
+ github.com/a-h/templ/cmd/templ
+ github.com/haatos/goshipit/cmd/gsi
+)
diff --git a/go.sum b/go.sum
index 1326dde..83f5068 100644
--- a/go.sum
+++ b/go.sum
@@ -26,6 +26,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/haatos/goshipit v0.0.0-20260102021700-fcb988ab74c5 h1:0nJIrglhp2shqGH+sM4dYcADM5w8gCT2ilE6mxdZcio=
+github.com/haatos/goshipit v0.0.0-20260102021700-fcb988ab74c5/go.mod h1:LFP8N8y5ORkifb+LZuOVNZYlJuV3WqdXCjxX5pGUaNI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
diff --git a/pkg/web/components/base/input.templ b/pkg/web/components/base/input.templ
new file mode 100644
index 0000000..87c5049
--- /dev/null
+++ b/pkg/web/components/base/input.templ
@@ -0,0 +1,88 @@
+package base
+
+type InputProps struct {
+ // common
+ Name string
+ Label string
+ Type string // defaults to "text"
+ Value string
+ Placeholder string
+ Description string
+ Error string
+ DisabledMessage string
+ Size string // xs sm lg xl, default: md
+ Required bool
+ Icon templ.Component
+ Attrs templ.Attributes
+
+ // text input
+ MinLength string
+ MaxLength string
+ Pattern string
+ ValidatorHint string
+
+ // integer/decimal input
+ Min string
+ Max string
+
+ // decimal input
+ Step string
+}
+
+templ Input(props InputProps) {
+
+}
diff --git a/pkg/web/components/base/input_templ.go b/pkg/web/components/base/input_templ.go
new file mode 100644
index 0000000..0e5a92f
--- /dev/null
+++ b/pkg/web/components/base/input_templ.go
@@ -0,0 +1,348 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.977
+package base
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+type InputProps struct {
+ // common
+ Name string
+ Label string
+ Type string // defaults to "text"
+ Value string
+ Placeholder string
+ Description string
+ Error string
+ DisabledMessage string
+ Size string // xs sm lg xl, default: md
+ Required bool
+ Icon templ.Component
+ Attrs templ.Attributes
+
+ // text input
+ MinLength string
+ MaxLength string
+ Pattern string
+ ValidatorHint string
+
+ // integer/decimal input
+ Min string
+ Max string
+
+ // decimal input
+ Step string
+}
+
+func Input(props InputProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/web/components/base/range.templ b/pkg/web/components/base/range.templ
new file mode 100644
index 0000000..fd00a11
--- /dev/null
+++ b/pkg/web/components/base/range.templ
@@ -0,0 +1,98 @@
+package base
+
+import "fmt"
+
+type RangeProps struct {
+ ID string
+ Label string
+ Name string
+ Value int
+ Min int
+ Max int
+ Step int
+ Class string
+ Size string
+}
+
+// Note: usage requires alpine.js
+templ Range(props RangeProps) {
+
+}
+
+// Note: usage requires datastar.js
+templ DatastarRange(props RangeProps) {
+
+}
diff --git a/pkg/web/components/base/range_templ.go b/pkg/web/components/base/range_templ.go
new file mode 100644
index 0000000..47e5f26
--- /dev/null
+++ b/pkg/web/components/base/range_templ.go
@@ -0,0 +1,397 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.977
+package base
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "fmt"
+
+type RangeProps struct {
+ ID string
+ Label string
+ Name string
+ Value int
+ Min int
+ Max int
+ Step int
+ Class string
+ Size string
+}
+
+// Note: usage requires alpine.js
+func Range(props RangeProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+// Note: usage requires datastar.js
+func DatastarRange(props RangeProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var13 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var13 == nil {
+ templ_7745c5c3_Var13 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/web/components/base/select.templ b/pkg/web/components/base/select.templ
new file mode 100644
index 0000000..9a14c81
--- /dev/null
+++ b/pkg/web/components/base/select.templ
@@ -0,0 +1,67 @@
+package base
+
+type SelectProps struct {
+ ID string
+ Label string
+ Name string
+ Description string
+ Class string
+ Size string
+ Required bool
+ Options []SelectOption
+ Attrs templ.Attributes
+}
+
+type SelectOption struct {
+ Label string
+ Value string
+ Selected bool
+ Disabled bool
+}
+
+templ Select(props SelectProps) {
+
+}
+
+templ SelectOptions(options []SelectOption) {
+ for i := range options {
+
+ }
+}
diff --git a/pkg/web/components/base/select_templ.go b/pkg/web/components/base/select_templ.go
new file mode 100644
index 0000000..121dbe8
--- /dev/null
+++ b/pkg/web/components/base/select_templ.go
@@ -0,0 +1,260 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.977
+package base
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+type SelectProps struct {
+ ID string
+ Label string
+ Name string
+ Description string
+ Class string
+ Size string
+ Required bool
+ Options []SelectOption
+ Attrs templ.Attributes
+}
+
+type SelectOption struct {
+ Label string
+ Value string
+ Selected bool
+ Disabled bool
+}
+
+func Select(props SelectProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func SelectOptions(options []SelectOption) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var8 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var8 == nil {
+ templ_7745c5c3_Var8 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ for i := range options {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/web/components/base/table.templ b/pkg/web/components/base/table.templ
new file mode 100644
index 0000000..d24c844
--- /dev/null
+++ b/pkg/web/components/base/table.templ
@@ -0,0 +1,28 @@
+package base
+
+templ Table(headers []templ.Component, rows []templ.Component, attrs templ.Attributes) {
+
+
+
+
+ for _, header := range headers {
+ |
+ @header
+ |
+ }
+
+
+
+ for _, trow := range rows {
+ @trow
+ }
+
+
+
+}
+
+// Component to use as plain text when
+// templ.Component is used as argument
+templ PlainText(content string) {
+ { content }
+}
diff --git a/pkg/web/components/base/table_templ.go b/pkg/web/components/base/table_templ.go
new file mode 100644
index 0000000..dc224b4
--- /dev/null
+++ b/pkg/web/components/base/table_templ.go
@@ -0,0 +1,112 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.977
+package base
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+func Table(headers []templ.Component, rows []templ.Component, attrs templ.Attributes) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, header := range headers {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "| ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = header.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " | ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, trow := range rows {
+ templ_7745c5c3_Err = trow.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+// Component to use as plain text when
+// templ.Component is used as argument
+func PlainText(content string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var2 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var2 == nil {
+ templ_7745c5c3_Var2 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(content)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/base/table.templ`, Line: 27, Col: 10}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/web/components/base/textarea.templ b/pkg/web/components/base/textarea.templ
new file mode 100644
index 0000000..f065a4f
--- /dev/null
+++ b/pkg/web/components/base/textarea.templ
@@ -0,0 +1,57 @@
+package base
+
+import "fmt"
+
+type TextareaProps struct {
+ ID string
+ Label string
+ Name string
+ Placeholder string
+ Value string
+ Description string
+ Err string
+ Class string
+ Size string
+ Required bool
+ Rows int
+ Attrs templ.Attributes
+}
+
+templ Textarea(props TextareaProps) {
+
+}
diff --git a/pkg/web/components/base/textarea_templ.go b/pkg/web/components/base/textarea_templ.go
new file mode 100644
index 0000000..81f436c
--- /dev/null
+++ b/pkg/web/components/base/textarea_templ.go
@@ -0,0 +1,241 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.977
+package base
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "fmt"
+
+type TextareaProps struct {
+ ID string
+ Label string
+ Name string
+ Placeholder string
+ Value string
+ Description string
+ Err string
+ Class string
+ Size string
+ Required bool
+ Rows int
+ Attrs templ.Attributes
+}
+
+func Textarea(props TextareaProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate