diff options
-rw-r--r-- | README.md | 91 | ||||
-rw-r--r-- | examples/simple/simple.go | 72 | ||||
-rw-r--r-- | examples/tailwindcss/tailwindcss.go | 125 | ||||
-rw-r--r-- | internal/examples/app/cmd/app/main.go | 13 | ||||
-rw-r--r-- | internal/examples/app/go.mod | 5 | ||||
-rw-r--r-- | internal/examples/app/go.sum | 2 | ||||
-rw-r--r-- | internal/examples/app/html/about.go | 25 | ||||
-rw-r--r-- | internal/examples/app/html/components.go | 62 | ||||
-rw-r--r-- | internal/examples/app/html/home.go | 20 | ||||
-rw-r--r-- | internal/examples/app/http/pages.go | 24 | ||||
-rw-r--r-- | internal/examples/app/http/server.go | 18 |
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 +} |