package webui import "kjol/vdom" // Port of web/kit/General.tsx. Anchor components render plain ; the app // wires SPA navigation on top (e.g. its layout nav uses Button/OnClick). // PageContainer wraps a page body (marker class kept for app CSS). func PageContainer(children ...*vdom.VNode) *vdom.VNode { return vdom.Div(kids([]vdom.Mod{vdom.Attr("class", "admin-page-container")}, children)...) } // Divider is a thin horizontal rule. func Divider() *vdom.VNode { return vdom.Hr(vdom.Attr("class", "text-neutral-200 mt-1 mb-3")) } // CodeBox renders a dark monospace code block. func CodeBox(code, class string) *vdom.VNode { return vdom.Div(vdom.Attr("class", cx("text-xs p-3 bg-neutral-800 text-neutral-100 border border-neutral-700 rounded-default", class)), vdom.Pre(vdom.Code(vdom.Text(code))), ) } // PageHeader is a centered page title with an underline. func PageHeader(text, class string) *vdom.VNode { return vdom.Header(vdom.Attr("class", class), vdom.Div(vdom.Attr("class", "mt-1"), vdom.H1(vdom.Attr("class", "text-center text-2xl font-light text-ink mb-2"), vdom.Text(text)), vdom.Hr(vdom.Attr("class", "text-neutral-200 mb-2")), ), ) } // PageLink is an inline text link (blue). newTab opens in a new tab. func PageLink(href string, newTab bool, class string, children ...*vdom.VNode) *vdom.VNode { mods := []vdom.Mod{ vdom.Attr("href", href), vdom.Attr("class", cx("text-sky-700 dark:text-sky-400 hover:text-sky-800 hover:underline hover:decoration-1", class)), } if newTab { mods = append(mods, vdom.Attr("target", "_blank"), vdom.Attr("rel", "noopener noreferrer")) } return vdom.A(kids(mods, children)...) } // Loader is a centered spinning ring. func Loader() *vdom.VNode { return vdom.Div(vdom.Attr("class", "flex items-center justify-center p-8"), vdom.Div(vdom.Attr("class", "h-8 w-8 border-4 border-sky-700 border-t-transparent rounded-full animate-spin")), ) } // BreadcrumbItem is one crumb (URL + display text). type BreadcrumbItem struct { URL string DisplayText string } // Breadcrumbs renders a chevron-separated crumb trail; the last item is bold and // not linked. func Breadcrumbs(items []BreadcrumbItem) *vdom.VNode { mods := []vdom.Mod{vdom.Attr("class", "flex flex-row items-center text-ink-faint text-xs")} for i, crumb := range items { if i != len(items)-1 { mods = append(mods, vdom.Span(vdom.Attr("class", "flex items-center"), vdom.A(vdom.Attr("href", crumb.URL), vdom.Attr("class", "text-ink-muted cursor-pointer no-underline hover:text-ink-soft hover:underline"), vdom.Text(crumb.DisplayText)), Icon("chevron-right", 12, "mx-[0.15rem] opacity-50"), )) } else { mods = append(mods, vdom.Span(vdom.Attr("class", "text-ink-soft font-medium"), vdom.Text(crumb.DisplayText))) } } return vdom.Div(mods...) } // ManagerPageHeader is a title/description row with an optional action slot // (marker classes kept for app CSS). func ManagerPageHeader(title, description string, action *vdom.VNode) *vdom.VNode { inner := vdom.Div(vdom.H2(vdom.Attr("class", "page-title"), vdom.Text(title))) if description != "" { inner.Children = append(inner.Children, vdom.P(vdom.Attr("class", "page-desc"), vdom.Text(description))) } mods := []vdom.Mod{vdom.Attr("class", "page-header"), inner} if action != nil { mods = append(mods, action) } return vdom.Div(mods...) }