initial port of the UI kit
This commit is contained in:
64
go/webui/webui.go
Normal file
64
go/webui/webui.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Package webui is a Go/WebAssembly UI component kit for the gowasm engine — a
|
||||
// direct port of kjol's Solid.js TSX kit (web/kit). Components are neutral
|
||||
// *vdom.VNode builders: they render to HTML on the server (SSR) and hydrate on
|
||||
// the client, styled with Tailwind utility classes.
|
||||
//
|
||||
// Port conventions (how the TSX maps to Go):
|
||||
//
|
||||
// - A TSX component `function Foo(props)` becomes `func Foo(p FooProps, children ...*vdom.VNode) *vdom.VNode`
|
||||
// (children variadic when the component wraps content).
|
||||
// - Solid's reactive accessors (`value | () => value`) collapse away: the whole
|
||||
// component re-renders on a signal write (React-style), so callers pass plain
|
||||
// current values — read a signal with `.Get()` at the call site.
|
||||
// - `onclick={fn}` → `vdom.On(vdom.EVENT_CLICK, fn)`; handlers needing the event use
|
||||
// `OnEvent`. `class` overrides are the trailing `Class` field, joined via cx.
|
||||
// - Tailwind classes are copied verbatim so kjol/cmd/twcss (scanning these .go
|
||||
// files) emits the matching CSS. Custom tokens (rounded-default, bg-primary,
|
||||
// text-text-heading, …) come from the app's @theme block.
|
||||
// - Browser-only behavior (floating-ui positioning, portals, focus traps,
|
||||
// element measurement) has no equivalent in the neutral runtime; those
|
||||
// components port their structure + Tailwind + signal/event wiring, and
|
||||
// approximate positioning with CSS where possible. Such gaps are marked
|
||||
// with a NOTE in the component's file.
|
||||
package webui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"kjol/vdom"
|
||||
)
|
||||
|
||||
// cx joins non-empty class fragments with single spaces (a tiny clsx). Trailing
|
||||
// user `Class` overrides go last so they win under equal specificity.
|
||||
func cx(parts ...string) string {
|
||||
var b strings.Builder
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteString(p)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// pick returns v if non-empty, else def (for defaulted string props like type).
|
||||
func pick(v, def string) string {
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// kids appends children VNodes onto a mod slice (helper for the props+children shape).
|
||||
func kids(mods []vdom.Mod, children []*vdom.VNode) []vdom.Mod {
|
||||
for _, c := range children {
|
||||
if c != nil {
|
||||
mods = append(mods, c)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
Reference in New Issue
Block a user