Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ bench/*.pprof
examples/simple/simple
examples/htmx/htmx
examples/todo/todo
examples/blog/blog
examples/htmx-render-target/htmx-render-target-example
examples/lint-misuse/lint-misuse

Expand Down
12 changes: 3 additions & 9 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,13 @@
Each example has its own `go.mod`. From the example directory:

```shell
# Generate templ files first (required)
templ generate -include-version=false
# Generate the .x.go files from .gsx sources first (required)
gsx generate .

# Run the server (defaults to :8080)
go run .
```

Or use templ's watch mode for live-reloading during development:

```shell
templ generate --watch --proxy="http://localhost:8080" --cmd="go run ."
```

You'll need:
- Go 1.24+
- `templ` CLI: `go install github.com/a-h/templ/cmd/templ@latest` (not required for `html-template/`, which uses only the standard library)
- `gsx` CLI: `go install github.com/gsxhq/gsx/cmd/gsx@latest` (not required for `html-template/`, which uses only the standard library)
2 changes: 1 addition & 1 deletion examples/blog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ to Go + templ.
## Run

```sh
templ generate -include-version=false
gsx generate ./admin ./blog ./ui/components ./ui/layout
go run .
# open http://localhost:8080 — admin login: admin / admin
```
Expand Down
145 changes: 145 additions & 0 deletions examples/blog/admin/components.gsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Admin-local gsx functions. StatsGrid, RecentPostsCard and PostsTable are
// standalone function components so the dashboard's Props+RenderTarget switch
// can refresh each widget independently with target.Is(StatsGrid) /
// target.Is(RecentPostsCard).
package admin

import (
"github.com/jackielii/structpages/examples/blog/store"
"github.com/jackielii/structpages/examples/blog/ui/components"
)

// StatCell — capitalized (gsx components must be Capitalized; the templ name
// was lowercase `statCell`).
component StatCell(label string, value int) {
<div class="rounded-lg border bg-white p-4 text-center shadow-sm">
<div class="text-3xl font-semibold text-slate-900">{ value }</div>
<div class="mt-1 text-xs uppercase tracking-wide text-slate-500">
{ label }
</div>
</div>
}

component StatsGrid(stats store.Stats) {
<div id={StatsGrid |> id} class="grid grid-cols-2 gap-3 md:grid-cols-4">
<StatCell label="Posts" value={stats.Posts}/>
<StatCell label="Drafts" value={stats.Drafts}/>
<StatCell label="Comments" value={stats.Comments}/>
<StatCell label="Categories" value={stats.Categories}/>
</div>
}

component RecentPostsCard(posts []store.Post) {
<div id={RecentPostsCard |> id}>
<components.Card title="Recent posts">
<ul class="divide-y text-sm">
{ if len(posts) == 0 {
<li class="py-2 text-slate-500">No posts yet.</li>
} }
{ for _, p := range posts {
<li class="flex items-center justify-between py-2">
<a
class="hover:underline"
href={postEditPage{} |> url("id", p.ID)}
>
{ p.Title }
</a>
{ if p.Published {
<span
class="rounded bg-emerald-100 px-2 py-0.5 text-xs text-emerald-800"
>
live
</span>
} else {
<span
class="rounded bg-slate-200 px-2 py-0.5 text-xs text-slate-700"
>
draft
</span>
} }
</li>
} }
</ul>
</components.Card>
</div>
}

component PostsTable(posts []store.Post) {
<div
id={PostsTable |> id}
class="overflow-hidden rounded-lg border bg-white shadow-sm"
>
<table class="w-full text-sm">
<thead
class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500"
>
<tr>
<th class="px-3 py-2">Title</th>
<th class="px-3 py-2">Status</th>
<th class="px-3 py-2">Created</th>
<th class="px-3 py-2"></th>
</tr>
</thead>
<tbody>
{ for _, p := range posts {
<tr class="border-t">
<td class="px-3 py-2 font-medium">
<a
class="hover:underline"
href={postEditPage{} |> url("id", p.ID)}
>
{ p.Title }
</a>
</td>
<td class="px-3 py-2">
{ if p.Published {
<span
class="rounded bg-emerald-100 px-2 py-0.5 text-xs text-emerald-800"
>
published
</span>
} else {
<span
class="rounded bg-slate-200 px-2 py-0.5 text-xs text-slate-700"
>
draft
</span>
} }
</td>
<td class="px-3 py-2 text-slate-500">
{ p.CreatedAt.Format("Jan 2, 2006") }
</td>
<td class="px-3 py-2 text-right">
<form
method="POST"
action={postDeleteHandler{} |> url("id", p.ID)}
hx-post={postDeleteHandler{} |> url("id", p.ID)}
hx-target={PostsTable |> target}
hx-swap="outerHTML"
hx-confirm="Delete this post?"
class="inline"
>
<button
class="text-xs text-red-600 hover:underline"
type="submit"
>
Delete
</button>
</form>
</td>
</tr>
} }
{ if len(posts) == 0 {
<tr>
<td
colspan="4"
class="px-3 py-6 text-center text-slate-500"
>
No posts yet.
</td>
</tr>
} }
</tbody>
</table>
</div>
}
98 changes: 0 additions & 98 deletions examples/blog/admin/components.templ

This file was deleted.

Loading