package app import ( . "kjol/vdom" ui "kjol/webui" ) // row is a flex/grid container helper (appends *VNode children as Mods). func row(class string, children ...*VNode) *VNode { mods := []Mod{Attr("class", class)} for _, c := range children { mods = append(mods, c) } return Div(mods...) } // kitSection wraps a labeled demo block in a card. func kitSection(title string, body ...*VNode) *VNode { return ui.Card("", ui.CardHeader("", Text(title)), row("flex flex-col gap-4", body...), ) } func ptRow(name, plan string, status *VNode) *VNode { td := func(cls string, c *VNode) *VNode { return El("td", Attr("class", "px-3 py-2 text-sm "+cls), c) } return El("tr", td("text-neutral-800", Text(name)), td("text-neutral-600", Text(plan)), El("td", Attr("class", "px-3 py-2 text-sm text-right"), status), ) } //gowasm:page /kit layout=app func KitPage(d Deps) func() *VNode { // Interactive demos own their state via signals (a write re-renders). tab := NewSignal(0) acc := NewSignal(0) notify := NewSignal(true) span := NewSignal("week") modalOpen := NewSignal(false) menuOpen := NewSignal(false) name := NewSignal("") email := NewSignal("") plan := NewSignal("pro") return func() *VNode { return Div(Attr("class", "space-y-8"), Div( H2(Attr("class", "text-2xl font-semibold tracking-tight text-text-heading"), Text("UI Kit")), P(Attr("class", "mt-1 text-neutral-500"), Text("The kjol/webui components, ported from the Solid.js kit and styled with Tailwind. "+ "Interactive components are driven by signals; overlays and floating elements render "+ "in their static form (see the note at the bottom).")), ), kitSection("Buttons", row("flex flex-wrap items-center gap-2", ui.Button(ui.ButtonProps{Color: ui.ButtonPrimary, Text: "Primary"}), ui.Button(ui.ButtonProps{Color: ui.ButtonGreen, Text: "Green"}), ui.Button(ui.ButtonProps{Color: ui.ButtonRed, Text: "Red"}), ui.Button(ui.ButtonProps{Color: ui.ButtonBlue, Text: "Blue"}), ui.Button(ui.ButtonProps{Color: ui.ButtonNeutral, Text: "Neutral"}), ), row("flex flex-wrap items-center gap-2", ui.Button(ui.ButtonProps{Color: ui.ButtonPrimary, Outline: true, Text: "Outline"}), ui.Button(ui.ButtonProps{Color: ui.ButtonRed, Outline: true, Text: "Danger"}), ui.Button(ui.ButtonProps{Color: ui.ButtonGreen, Small: true, Icon: "check", Text: "Small + icon"}), ui.Button(ui.ButtonProps{Color: ui.ButtonNeutral, Icon: "plus"}), ui.Button(ui.ButtonProps{Color: ui.ButtonPrimary, Text: "Disabled", Disabled: true}), ), ), kitSection("Badges", row("flex flex-wrap items-center gap-2", ui.Badge(ui.BadgeProps{Color: ui.BadgeGreen}, Text("active")), ui.Badge(ui.BadgeProps{Color: ui.BadgeRed}, Text("failed")), ui.Badge(ui.BadgeProps{Color: ui.BadgeBlue}, Text("info")), ui.Badge(ui.BadgeProps{Color: ui.BadgeAmber, Pill: true}, Text("pending")), ui.Badge(ui.BadgeProps{Color: ui.BadgeNeutral}, Text("default")), ui.Badge(ui.BadgeProps{Color: ui.BadgeMuted}, Text("muted")), ), ), kitSection("Alerts", ui.Alert(ui.AlertBlue, "Heads up", Text("An informational message with a header.")), ui.Alert(ui.AlertGreen, "", Text("A success alert without a header.")), ui.Alert(ui.AlertYellow, "Warning", Text("Something needs your attention.")), ui.Alert(ui.AlertRed, "Error", Text("Something went wrong.")), ), kitSection("Toggles & segmented control", ui.ToggleSwitch(notify.Get(), func(v bool) { notify.Set(v) }, "Email notifications", "Send me product updates", false, ""), ui.SegmentedButtons([]ui.SegmentedButtonOption{ {Value: "day", Label: "Day"}, {Value: "week", Label: "Week"}, {Value: "month", Label: "Month"}, }, span.Get(), func(v string) { span.Set(v) }, false, "max-w-xs"), ), kitSection("Tabs", ui.TabGroup(ui.TabGroupProps{ Items: []ui.TabItem{ {Title: "Overview", Content: P(Attr("class", "pt-3 text-sm text-neutral-600"), Text("The overview panel."))}, {Title: "Details", Content: P(Attr("class", "pt-3 text-sm text-neutral-600"), Text("The details panel."))}, {Title: "Activity", Badge: 3, Content: P(Attr("class", "pt-3 text-sm text-neutral-600"), Text("The activity panel (3 new)."))}, }, ActiveIndex: tab.Get(), OnTabChange: func(i int) { tab.Set(i) }, }), ), kitSection("Accordion", ui.SingleAccordion([]ui.AccordionItemData{ {Title: "What is gowasm?", Content: P(Attr("class", "text-sm text-neutral-600"), Text("A tiny Go→WebAssembly UI engine."))}, {Title: "Is it isomorphic?", Content: P(Attr("class", "text-sm text-neutral-600"), Text("Yes — the same Go renders on the server (SSR) and hydrates on the client."))}, {Title: "How is it styled?", Content: P(Attr("class", "text-sm text-neutral-600"), Text("Tailwind utility classes, compiled by kjol's native Tailwind engine."))}, }, acc.Get(), func(i int) { acc.Set(i) }), ), kitSection("Forms", row("grid gap-4 sm:grid-cols-3", row("flex flex-col gap-1", ui.FormLabel(ui.FormLabelProps{}, Text("Name")), ui.FormInput(ui.FormInputProps{Value: name.Get(), Placeholder: "Ada Lovelace", OnInput: func(v string) { name.Set(v) }})), row("flex flex-col gap-1", ui.FormLabel(ui.FormLabelProps{}, Text("Email")), ui.FormEmailInput(ui.FormInputProps{Value: email.Get(), Placeholder: "ada@example.com", OnInput: func(v string) { email.Set(v) }}, true)), row("flex flex-col gap-1", ui.FormLabel(ui.FormLabelProps{}, Text("Plan")), ui.FormSelect(ui.FormSelectProps{Value: plan.Get(), OnChange: func(v string) { plan.Set(v) }}, ui.FormOption("free", "Free", false), ui.FormOption("pro", "Pro", false), ui.FormOption("enterprise", "Enterprise", false))), ), P(Attr("class", "text-xs text-neutral-500"), Text("Live: name=\""+name.Get()+"\" email=\""+email.Get()+"\" plan=\""+plan.Get()+"\"")), ), kitSection("Table", ui.PrettyTable( []ui.PrettyTableColumn{ {DisplayName: "Name"}, {DisplayName: "Plan"}, {DisplayName: "Status", DisplayPosition: ui.PrettyTableColRight}, }, ui.PrettyTableOptions{Hover: true, Alternate: true, SurroundingBorder: true, HeaderBorderY: true}, ptRow("Ada Lovelace", "Pro", ui.Badge(ui.BadgeProps{Color: ui.BadgeGreen}, Text("active"))), ptRow("Alan Turing", "Free", ui.Badge(ui.BadgeProps{Color: ui.BadgeNeutral}, Text("trial"))), ptRow("Grace Hopper", "Enterprise", ui.Badge(ui.BadgeProps{Color: ui.BadgeBlue}, Text("invited"))), ), ), kitSection("Overlays (interactive)", row("flex flex-wrap items-center gap-4", // Modal, toggled by a signal. ui.Button(ui.ButtonProps{Color: ui.ButtonPrimary, Text: "Open modal", OnClick: func() { modalOpen.Set(true) }}), // Menu, toggled by a signal. ui.Menu("relative inline-block", ui.MenuTrigger(func() { menuOpen.Set(!menuOpen.Get()) }, "", ui.Button(ui.ButtonProps{Color: ui.ButtonWhite, Text: "Menu ▾"})), ui.MenuContent(menuOpen.Get(), ui.MenuPlacementBottomStart, "", ui.MenuItem(ui.MenuItemProps{Icon: "check", OnClick: func() { menuOpen.Set(false) }}, Text("Profile")), ui.MenuItem(ui.MenuItemProps{OnClick: func() { menuOpen.Set(false) }}, Text("Settings")), ui.MenuDivider(""), ui.MenuItem(ui.MenuItemProps{OnClick: func() { menuOpen.Set(false) }}, Text("Sign out")), ), ), // Tooltip (pure CSS hover). ui.HoverTooltip(Span(Text("A CSS-only tooltip")), "top", "", ui.Button(ui.ButtonProps{Color: ui.ButtonNeutral, Outline: true, Text: "Hover me"})), ), ui.Modal(ui.ModalProps{ IsOpen: modalOpen.Get(), OnClose: func() { modalOpen.Set(false) }, Size: ui.ModalMedium, Header: H3(Attr("class", "text-lg font-semibold text-text-heading"), Text("Example modal")), }, P(Attr("class", "text-neutral-600"), Text("This modal is toggled by a signal; the backdrop and close button round-trip through OnClose.")), ), ), ui.Alert(ui.AlertGray, "About this page", Text("Floating/positioned components (menus, tooltips, modals, dropdowns) are ported as "+ "structure + Tailwind + signal/event wiring — the neutral runtime has no floating-ui, "+ "portals, or element measurement, so positioning is approximated with static classes.")), ) } }