about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.md91
-rw-r--r--examples/simple/simple.go72
-rw-r--r--examples/tailwindcss/tailwindcss.go125
-rw-r--r--internal/examples/app/cmd/app/main.go13
-rw-r--r--internal/examples/app/go.mod5
-rw-r--r--internal/examples/app/go.sum2
-rw-r--r--internal/examples/app/html/about.go25
-rw-r--r--internal/examples/app/html/components.go62
-rw-r--r--internal/examples/app/html/home.go20
-rw-r--r--internal/examples/app/http/pages.go24
-rw-r--r--internal/examples/app/http/server.go18
11 files changed, 177 insertions, 280 deletions
diff --git a/README.md b/README.md
index cb8bd7e..1650ba4 100644
--- a/README.md
+++ b/README.md
@@ -39,109 +39,34 @@ Get the library using `go get`:
 go get github.com/maragudk/gomponents
 ```
 
-The preferred way to use gomponents is with so-called dot-imports (note the dot before the `gomponents/html` import),
+The preferred way to use gomponents is with so-called dot-imports (note the dot before the imports),
 to give you that smooth, native HTML feel:
 
 ```go
 package main
 
 import (
-	"net/http"
-
-	g "github.com/maragudk/gomponents"
-	c "github.com/maragudk/gomponents/components"
+	. "github.com/maragudk/gomponents"
+	. "github.com/maragudk/gomponents/components"
 	. "github.com/maragudk/gomponents/html"
 )
 
-func main() {
-	_ = http.ListenAndServe("localhost:8080", http.HandlerFunc(handler))
-}
-
-func handler(w http.ResponseWriter, r *http.Request) {
-	_ = Page("Hi!", r.URL.Path).Render(w)
-}
-
-func Page(title, currentPath string) g.Node {
-	return Doctype(
-		HTML(
-			Lang("en"),
-			Head(
-				TitleEl(g.Text(title)),
-				StyleEl(Type("text/css"), g.Raw(".is-active{ font-weight: bold }")),
-			),
-			Body(
-				Navbar(currentPath),
-				H1(g.Text(title)),
-				P(g.Textf("Welcome to the page at %v.", currentPath)),
-			),
-		),
-	)
-}
-
-func Navbar(currentPath string) g.Node {
+func Navbar(authenticated bool, currentPath string) Node {
 	return Nav(
 		NavbarLink("/", "Home", currentPath),
 		NavbarLink("/about", "About", currentPath),
+		If(authenticated, NavbarLink("/profile", "Profile", currentPath)),
 	)
 }
 
-func NavbarLink(href, name, currentPath string) g.Node {
-	return A(Href(href), c.Classes{"is-active": currentPath == href}, g.Text(name))
+func NavbarLink(href, name, currentPath string) Node {
+	return A(Href(href), Classes{"is-active": currentPath == href}, g.Text(name))
 }
 ```
 
 Some people don't like dot-imports, and luckily it's completely optional.
-If you don't like dot-imports, just use regular imports.
-
-You could also use the provided HTML5 document template to simplify your code a bit:
-
-```go
-package main
-
-import (
-	"net/http"
-
-	g "github.com/maragudk/gomponents"
-	c "github.com/maragudk/gomponents/components"
-	. "github.com/maragudk/gomponents/html"
-)
-
-func main() {
-	_ = http.ListenAndServe("localhost:8080", http.HandlerFunc(handler))
-}
-
-func handler(w http.ResponseWriter, r *http.Request) {
-	_ = Page("Hi!", r.URL.Path).Render(w)
-}
-
-func Page(title, currentPath string) g.Node {
-	return c.HTML5(c.HTML5Props{
-		Title:    title,
-		Language: "en",
-		Head: []g.Node{
-			StyleEl(Type("text/css"), g.Raw(".is-active{ font-weight: bold }")),
-		},
-		Body: []g.Node{
-			Navbar(currentPath),
-			H1(g.Text(title)),
-			P(g.Textf("Welcome to the page at %v.", currentPath)),
-		},
-	})
-}
-
-func Navbar(currentPath string) g.Node {
-	return Nav(
-		NavbarLink("/", "Home", currentPath),
-		NavbarLink("/about", "About", currentPath),
-	)
-}
-
-func NavbarLink(href, name, currentPath string) g.Node {
-	return A(Href(href), c.Classes{"is-active": currentPath == href}, g.Text(name))
-}
-```
 
-For more complete examples, see [the examples directory](examples/).
+For a more complete example, see [the examples directory](internal/examples/).
 
 ### What's up with the specially named elements and attributes?
 
diff --git a/examples/simple/simple.go b/examples/simple/simple.go
deleted file mode 100644
index 20c41fb..0000000
--- a/examples/simple/simple.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package main
-
-import (
-	"net/http"
-
-	g "github.com/maragudk/gomponents"
-	c "github.com/maragudk/gomponents/components"
-	. "github.com/maragudk/gomponents/html"
-)
-
-func main() {
-	_ = http.ListenAndServe("localhost:8080", http.HandlerFunc(handler))
-}
-
-func handler(w http.ResponseWriter, r *http.Request) {
-	_ = Page(props{
-		title: r.URL.Path,
-		path:  r.URL.Path,
-	}).Render(w)
-}
-
-type props struct {
-	title string
-	path  string
-}
-
-// Page is a whole document to output.
-func Page(p props) g.Node {
-	return c.HTML5(c.HTML5Props{
-		Title:    p.title,
-		Language: "en",
-		Head: []g.Node{
-			StyleEl(Type("text/css"),
-				g.Raw("html { font-family: sans-serif; }"),
-				g.Raw("ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; }"),
-				g.Raw("ul li { display: block; padding: 8px; float: left; }"),
-				g.Raw(".is-active { font-weight: bold; }"),
-			),
-		},
-		Body: []g.Node{
-			Navbar(p.path, []PageLink{
-				{Path: "/foo", Name: "Foo"},
-				{Path: "/bar", Name: "Bar"},
-			}),
-			H1(g.Text(p.title)),
-			P(g.Textf("Welcome to the page at %v.", p.path)),
-		},
-	})
-}
-
-type PageLink struct {
-	Path string
-	Name string
-}
-
-func Navbar(currentPath string, links []PageLink) g.Node {
-	return Div(
-		Ul(
-			NavbarLink("/", "Home", currentPath),
-
-			g.Group(g.Map(links, func(pl PageLink) g.Node {
-				return NavbarLink(pl.Path, pl.Name, currentPath)
-			})),
-		),
-
-		Hr(),
-	)
-}
-
-func NavbarLink(href, name, currentPath string) g.Node {
-	return Li(A(Href(href), c.Classes{"is-active": currentPath == href}, g.Text(name)))
-}
diff --git a/examples/tailwindcss/tailwindcss.go b/examples/tailwindcss/tailwindcss.go
deleted file mode 100644
index 6e92288..0000000
--- a/examples/tailwindcss/tailwindcss.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package main
-
-import (
-	"net/http"
-	"time"
-
-	g "github.com/maragudk/gomponents"
-	c "github.com/maragudk/gomponents/components"
-	. "github.com/maragudk/gomponents/html"
-)
-
-func main() {
-	http.Handle("/", createHandler(indexPage()))
-	http.Handle("/contact", createHandler(contactPage()))
-	http.Handle("/about", createHandler(aboutPage()))
-
-	_ = http.ListenAndServe("localhost:8080", nil)
-}
-
-func createHandler(title string, body g.Node) http.HandlerFunc {
-	return func(w http.ResponseWriter, r *http.Request) {
-		// Rendering a Node is as simple as calling Render and passing an io.Writer
-		_ = Page(title, r.URL.Path, body).Render(w)
-	}
-}
-
-func indexPage() (string, g.Node) {
-	return "Welcome!", Div(
-		H1(g.Text("Welcome to this example page")),
-		P(g.Text("I hope it will make you happy. ๐Ÿ˜„ It's using TailwindCSS for styling.")),
-	)
-}
-
-func contactPage() (string, g.Node) {
-	return "Contact", Div(
-		H1(g.Text("Contact us")),
-		P(g.Text("Just do it.")),
-	)
-}
-
-func aboutPage() (string, g.Node) {
-	return "About", Div(
-		H1(g.Text("About this site")),
-		P(g.Text("This is a site showing off gomponents.")),
-	)
-}
-
-func Page(title, path string, body g.Node) g.Node {
-	// HTML5 boilerplate document
-	return c.HTML5(c.HTML5Props{
-		Title:    title,
-		Language: "en",
-		Head: []g.Node{
-			Link(Rel("stylesheet"), Href("https://unpkg.com/tailwindcss@2.1.2/dist/base.min.css")),
-			Link(Rel("stylesheet"), Href("https://unpkg.com/tailwindcss@2.1.2/dist/components.min.css")),
-			Link(Rel("stylesheet"), Href("https://unpkg.com/@tailwindcss/typography@0.4.0/dist/typography.min.css")),
-			Link(Rel("stylesheet"), Href("https://unpkg.com/tailwindcss@2.1.2/dist/utilities.min.css")),
-		},
-		Body: []g.Node{
-			Navbar(path, []PageLink{
-				{Path: "/contact", Name: "Contact"},
-				{Path: "/about", Name: "About"},
-			}),
-			Container(
-				Prose(body),
-				PageFooter(),
-			),
-		},
-	})
-}
-
-type PageLink struct {
-	Path string
-	Name string
-}
-
-func Navbar(currentPath string, links []PageLink) g.Node {
-	return Nav(Class("bg-gray-700 mb-4"),
-		Container(
-			Div(Class("flex items-center space-x-4 h-16"),
-				NavbarLink("/", "Home", currentPath == "/"),
-
-				// We can Map custom slices to Nodes
-				g.Group(g.Map(links, func(pl PageLink) g.Node {
-					return NavbarLink(pl.Path, pl.Name, currentPath == pl.Path)
-				})),
-			),
-		),
-	)
-}
-
-// NavbarLink is a link in the Navbar.
-func NavbarLink(path, text string, active bool) g.Node {
-	return A(Href(path), g.Text(text),
-		// Apply CSS classes conditionally
-		c.Classes{
-			"px-3 py-2 rounded-md text-sm font-medium focus:outline-none focus:text-white focus:bg-gray-700": true,
-			"text-white bg-gray-900":                           active,
-			"text-gray-300 hover:text-white hover:bg-gray-700": !active,
-		},
-	)
-}
-
-func Container(children ...g.Node) g.Node {
-	return Div(Class("max-w-7xl mx-auto px-2 sm:px-6 lg:px-8"), g.Group(children))
-}
-
-func Prose(children ...g.Node) g.Node {
-	return Div(Class("prose"), g.Group(children))
-}
-
-func PageFooter() g.Node {
-	return Footer(Class("prose prose-sm prose-indigo"),
-		P(
-			// We can use string interpolation directly, like fmt.Sprintf.
-			g.Textf("Rendered %v. ", time.Now().Format(time.RFC3339)),
-
-			// Conditional inclusion
-			g.If(time.Now().Second()%2 == 0, g.Text("It's an even second.")),
-			g.If(time.Now().Second()%2 == 1, g.Text("It's an odd second.")),
-		),
-
-		P(A(Href("https://www.gomponents.com"), g.Text("gomponents"))),
-	)
-}
diff --git a/internal/examples/app/cmd/app/main.go b/internal/examples/app/cmd/app/main.go
new file mode 100644
index 0000000..0c01576
--- /dev/null
+++ b/internal/examples/app/cmd/app/main.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+	"log/slog"
+
+	"app/http"
+)
+
+func main() {
+	if err := http.Start(); err != nil {
+		slog.Info("Error", 1, "error", err)
+	}
+}
diff --git a/internal/examples/app/go.mod b/internal/examples/app/go.mod
new file mode 100644
index 0000000..a86bad6
--- /dev/null
+++ b/internal/examples/app/go.mod
@@ -0,0 +1,5 @@
+module app
+
+go 1.23.1
+
+require github.com/maragudk/gomponents v0.20.5
diff --git a/internal/examples/app/go.sum b/internal/examples/app/go.sum
new file mode 100644
index 0000000..37a89f1
--- /dev/null
+++ b/internal/examples/app/go.sum
@@ -0,0 +1,2 @@
+github.com/maragudk/gomponents v0.20.5 h1:Z8phdQIW+NqAGO0clMh/wyv1/M7viUBf7/EL44gvwtI=
+github.com/maragudk/gomponents v0.20.5/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg=
diff --git a/internal/examples/app/html/about.go b/internal/examples/app/html/about.go
new file mode 100644
index 0000000..37b797f
--- /dev/null
+++ b/internal/examples/app/html/about.go
@@ -0,0 +1,25 @@
+package html
+
+import (
+	"time"
+
+	. "github.com/maragudk/gomponents"
+	. "github.com/maragudk/gomponents/html"
+)
+
+func AboutPage() Node {
+	now := time.Now()
+
+	return page("About",
+		H1(Text("About")),
+
+		P(Textf("Built with gomponents and rendered at %v.", now.Format(time.TimeOnly))),
+
+		P(
+			If(now.Second()%2 == 0, Text("It's an even second!")),
+			If(now.Second()%2 != 0, Text("It's an odd second!")),
+		),
+
+		Img(Class("max-w-sm"), Src("https://www.gomponents.com/images/logo.png"), Alt("gomponents logo")),
+	)
+}
diff --git a/internal/examples/app/html/components.go b/internal/examples/app/html/components.go
new file mode 100644
index 0000000..25880a3
--- /dev/null
+++ b/internal/examples/app/html/components.go
@@ -0,0 +1,62 @@
+package html
+
+import (
+	. "github.com/maragudk/gomponents"
+	. "github.com/maragudk/gomponents/components"
+	. "github.com/maragudk/gomponents/html"
+)
+
+func page(title string, children ...Node) Node {
+	return HTML5(HTML5Props{
+		Title:    title,
+		Language: "en",
+		Head: []Node{
+			Script(Src("https://cdn.tailwindcss.com?plugins=typography")),
+		},
+		Body: []Node{Class("bg-gradient-to-b from-white to-indigo-100 bg-no-repeat"),
+			Div(Class("min-h-screen flex flex-col justify-between"),
+				header(),
+				Div(Class("grow"),
+					container(true,
+						Div(Class("prose prose-lg prose-indigo"),
+							Group(children),
+						),
+					),
+				),
+				footer(),
+			),
+		},
+	})
+}
+
+func header() Node {
+	return Div(Class("bg-indigo-600 text-white shadow"),
+		container(false,
+			Div(Class("flex items-center space-x-4 h-8"),
+				headerLink("/", "Home"),
+				headerLink("/about", "About"),
+			),
+		),
+	)
+}
+
+func headerLink(href, text string) Node {
+	return A(Class("hover:text-indigo-300"), Href(href), Text(text))
+}
+
+func container(padY bool, children ...Node) Node {
+	return Div(
+		Classes{
+			"max-w-7xl mx-auto":     true,
+			"px-4 md:px-8 lg:px-16": true,
+			"py-4 md:py-8":          padY,
+		},
+		Group(children),
+	)
+}
+
+func footer() Node {
+	return Div(Class("bg-gray-900 text-white shadow text-center h-16 flex items-center justify-center"),
+		A(Href("https://www.gomponents.com"), Text("gomponents")),
+	)
+}
diff --git a/internal/examples/app/html/home.go b/internal/examples/app/html/home.go
new file mode 100644
index 0000000..d4e192c
--- /dev/null
+++ b/internal/examples/app/html/home.go
@@ -0,0 +1,20 @@
+package html
+
+import (
+	. "github.com/maragudk/gomponents"
+	. "github.com/maragudk/gomponents/html"
+)
+
+func HomePage(items []string) Node {
+	return page("Home",
+		H1(Text("Home")),
+
+		P(Text("This is a gomponents example app!")),
+
+		P(Raw(`Have a look at the <a href="https://github.com/maragudk/gomponents/tree/main/internal/examples/app">source code</a> to see how itโ€™s structured.`)),
+
+		Ul(Group(Map(items, func(s string) Node {
+			return Li(Text(s))
+		}))),
+	)
+}
diff --git a/internal/examples/app/http/pages.go b/internal/examples/app/http/pages.go
new file mode 100644
index 0000000..dd686c0
--- /dev/null
+++ b/internal/examples/app/http/pages.go
@@ -0,0 +1,24 @@
+package http
+
+import (
+	"net/http"
+
+	g "github.com/maragudk/gomponents"
+	ghttp "github.com/maragudk/gomponents/http"
+
+	"app/html"
+)
+
+func Home(mux *http.ServeMux) {
+	mux.Handle("GET /", ghttp.Adapt(func(w http.ResponseWriter, r *http.Request) (g.Node, error) {
+		// Let's pretend this comes from a db or something.
+		items := []string{"Super", "Duper", "Nice"}
+		return html.HomePage(items), nil
+	}))
+}
+
+func About(mux *http.ServeMux) {
+	mux.Handle("GET /about", ghttp.Adapt(func(w http.ResponseWriter, r *http.Request) (g.Node, error) {
+		return html.AboutPage(), nil
+	}))
+}
diff --git a/internal/examples/app/http/server.go b/internal/examples/app/http/server.go
new file mode 100644
index 0000000..8264fcf
--- /dev/null
+++ b/internal/examples/app/http/server.go
@@ -0,0 +1,18 @@
+package http
+
+import (
+	"net/http"
+)
+
+func Start() error {
+	return http.ListenAndServe(":8080", setupRoutes())
+}
+
+func setupRoutes() http.Handler {
+	mux := http.NewServeMux()
+
+	Home(mux)
+	About(mux)
+
+	return mux
+}