fix: some elements are not templui components
All checks were successful
Deploy / build-and-deploy (push) Successful in 2m17s

This commit is contained in:
juancwu 2026-02-15 05:17:30 +00:00
commit e5941e1329
7 changed files with 175 additions and 104 deletions

View file

@ -299,18 +299,20 @@ templ ItemSelectorSection(listsWithItems []model.ListWithUncheckedItems, oob boo
"_": "on change repeat for cb in <input[name='item_ids']/> in #" + itemsID + " set cb.checked to my.checked end",
},
})
<button
type="button"
id={ toggleID }
class="flex-1 flex items-center gap-1 text-sm font-medium cursor-pointer select-none"
_={ "on click toggle .hidden on #" + itemsID + " then toggle .rotate-90 on <svg/> in me" }
>
@button.Button(button.Props{
ID: toggleID,
Variant: button.VariantGhost,
Class: "flex-1 h-auto p-0 justify-start gap-1 text-sm font-medium select-none",
Attributes: templ.Attributes{
"_": "on click toggle .hidden on #" + itemsID + " then toggle .rotate-90 on <svg/> in me",
},
}) {
@icon.ChevronRight(icon.Props{Size: 14})
{ lwi.List.Name }
<span class="text-muted-foreground">
({ strconv.Itoa(len(lwi.Items)) })
</span>
</button>
}
</div>
<div id={ itemsID } class="hidden pl-6 space-y-1">
for _, item := range lwi.Items {

View file

@ -11,6 +11,7 @@ import (
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/label"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/radio"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/selectbox"
)
func methodDisplay(m *model.PaymentMethod) string {
@ -240,29 +241,29 @@ templ EditMethodForm(spaceID string, method *model.PaymentMethod, dialogID strin
templ MethodSelector(methods []*model.PaymentMethod, selectedMethodID *string) {
<div>
@label.Label(label.Props{For: "method-select"}) {
@label.Label(label.Props{}) {
Payment Method
}
<select
name="payment_method_id"
id="method-select"
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
<option value="">Cash</option>
for _, m := range methods {
<option
value={ m.ID }
if selectedMethodID != nil && *selectedMethodID == m.ID {
selected
}
>
if m.LastFour != nil {
{ m.Name } (*{ *m.LastFour })
} else {
{ m.Name }
}
</option>
@selectbox.SelectBox() {
@selectbox.Trigger(selectbox.TriggerProps{Name: "payment_method_id"}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Cash"})
}
</select>
@selectbox.Content(selectbox.ContentProps{NoSearch: len(methods) <= 5}) {
@selectbox.Item(selectbox.ItemProps{Value: "", Selected: selectedMethodID == nil}) {
Cash
}
for _, m := range methods {
if m.LastFour != nil {
@selectbox.Item(selectbox.ItemProps{Value: m.ID, Selected: selectedMethodID != nil && *selectedMethodID == m.ID}) {
{ m.Name } (*{ *m.LastFour })
}
} else {
@selectbox.Item(selectbox.ItemProps{Value: m.ID, Selected: selectedMethodID != nil && *selectedMethodID == m.ID}) {
{ m.Name }
}
}
}
}
}
</div>
}

View file

@ -13,6 +13,7 @@ import (
"git.juancwu.dev/juancwu/budgit/internal/ui/components/label"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/paymentmethod"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/radio"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/selectbox"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/tagsinput"
)
@ -216,16 +217,31 @@ templ AddRecurringForm(spaceID string, tags []*model.Tag, methods []*model.Payme
</div>
// Frequency
<div>
@label.Label(label.Props{For: "recurring-frequency"}) {
@label.Label(label.Props{}) {
Frequency
}
<select name="frequency" id="recurring-frequency" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background" required>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="biweekly">Biweekly</option>
<option value="monthly" selected>Monthly</option>
<option value="yearly">Yearly</option>
</select>
@selectbox.SelectBox(selectbox.Props{ID: "recurring-frequency"}) {
@selectbox.Trigger(selectbox.TriggerProps{Name: "frequency"}) {
@selectbox.Value()
}
@selectbox.Content(selectbox.ContentProps{NoSearch: true}) {
@selectbox.Item(selectbox.ItemProps{Value: "daily"}) {
Daily
}
@selectbox.Item(selectbox.ItemProps{Value: "weekly"}) {
Weekly
}
@selectbox.Item(selectbox.ItemProps{Value: "biweekly"}) {
Biweekly
}
@selectbox.Item(selectbox.ItemProps{Value: "monthly", Selected: true}) {
Monthly
}
@selectbox.Item(selectbox.ItemProps{Value: "yearly"}) {
Yearly
}
}
}
</div>
// Start Date
<div>
@ -345,16 +361,31 @@ templ EditRecurringForm(spaceID string, re *model.RecurringExpenseWithTagsAndMet
</div>
// Frequency
<div>
@label.Label(label.Props{For: "edit-recurring-freq-" + re.ID}) {
@label.Label(label.Props{}) {
Frequency
}
<select name="frequency" id={ "edit-recurring-freq-" + re.ID } class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background" required>
<option value="daily" selected?={ re.Frequency == model.FrequencyDaily }>Daily</option>
<option value="weekly" selected?={ re.Frequency == model.FrequencyWeekly }>Weekly</option>
<option value="biweekly" selected?={ re.Frequency == model.FrequencyBiweekly }>Biweekly</option>
<option value="monthly" selected?={ re.Frequency == model.FrequencyMonthly }>Monthly</option>
<option value="yearly" selected?={ re.Frequency == model.FrequencyYearly }>Yearly</option>
</select>
@selectbox.SelectBox(selectbox.Props{ID: "edit-recurring-freq-" + re.ID}) {
@selectbox.Trigger(selectbox.TriggerProps{Name: "frequency"}) {
@selectbox.Value()
}
@selectbox.Content(selectbox.ContentProps{NoSearch: true}) {
@selectbox.Item(selectbox.ItemProps{Value: "daily", Selected: re.Frequency == model.FrequencyDaily}) {
Daily
}
@selectbox.Item(selectbox.ItemProps{Value: "weekly", Selected: re.Frequency == model.FrequencyWeekly}) {
Weekly
}
@selectbox.Item(selectbox.ItemProps{Value: "biweekly", Selected: re.Frequency == model.FrequencyBiweekly}) {
Biweekly
}
@selectbox.Item(selectbox.ItemProps{Value: "monthly", Selected: re.Frequency == model.FrequencyMonthly}) {
Monthly
}
@selectbox.Item(selectbox.ItemProps{Value: "yearly", Selected: re.Frequency == model.FrequencyYearly}) {
Yearly
}
}
}
</div>
// Start Date
<div>

View file

@ -58,21 +58,25 @@ templ ListCardHeader(spaceID string, list *model.ShoppingList) {
<div id={ "lch-" + list.ID } class="flex items-center justify-between gap-2">
<h3 class="font-semibold truncate">{ list.Name }</h3>
<div class="flex items-center gap-1 shrink-0">
<button
type="button"
class="text-muted-foreground hover:text-foreground p-1 rounded hover:bg-muted transition-colors"
_={ fmt.Sprintf("on click toggle .hidden on #lch-%s then toggle .hidden on #lche-%s then focus() the first <input/> in #lche-%s", list.ID, list.ID, list.ID) }
>
@button.Button(button.Props{
Variant: button.VariantGhost,
Size: button.SizeIcon,
Class: "size-7 text-muted-foreground hover:text-foreground",
Attributes: templ.Attributes{
"_": fmt.Sprintf("on click toggle .hidden on #lch-%s then toggle .hidden on #lche-%s then focus() the first <input/> in #lche-%s", list.ID, list.ID, list.ID),
},
}) {
@icon.Pencil(icon.Props{Size: 14})
</button>
}
@dialog.Dialog(dialog.Props{ID: "del-list-" + list.ID}) {
@dialog.Trigger() {
<button
type="button"
class="text-muted-foreground hover:text-destructive p-1 rounded hover:bg-muted transition-colors"
>
@button.Button(button.Props{
Variant: button.VariantGhost,
Size: button.SizeIcon,
Class: "size-7 text-muted-foreground hover:text-destructive",
}) {
@icon.Trash2(icon.Props{Size: 14})
</button>
}
}
@dialog.Content() {
@dialog.Header() {
@ -121,16 +125,18 @@ templ ListCardHeader(spaceID string, list *model.ShoppingList) {
"required": "true",
},
})
<button type="submit" class="inline-flex items-center justify-center rounded-md text-sm font-medium h-8 px-3 bg-primary text-primary-foreground hover:bg-primary/90">
@button.Submit(button.Props{Size: button.SizeSm}) {
Save
</button>
<button
type="button"
class="inline-flex items-center justify-center rounded-md text-sm font-medium h-8 px-3 border hover:bg-muted"
_={ fmt.Sprintf("on click toggle .hidden on #lche-%s then toggle .hidden on #lch-%s", list.ID, list.ID) }
>
}
@button.Button(button.Props{
Variant: button.VariantOutline,
Size: button.SizeSm,
Attributes: templ.Attributes{
"_": fmt.Sprintf("on click toggle .hidden on #lche-%s then toggle .hidden on #lch-%s", list.ID, list.ID),
},
}) {
Cancel
</button>
}
</form>
}
@ -203,15 +209,18 @@ templ CardItemDetail(spaceID string, item *model.ListItem) {
},
})
<span class={ "text-sm flex-1", templ.KV("line-through text-muted-foreground", item.IsChecked) }>{ item.Name }</span>
<button
type="button"
class="text-muted-foreground hover:text-destructive p-1 rounded shrink-0"
hx-delete={ fmt.Sprintf("/app/spaces/%s/lists/%s/items/%s", spaceID, item.ListID, item.ID) }
hx-swap="none"
_={ fmt.Sprintf("on htmx:afterRequest send refreshItems to #list-items-%s", item.ListID) }
>
@button.Button(button.Props{
Variant: button.VariantGhost,
Size: button.SizeIcon,
Class: "size-7 text-muted-foreground hover:text-destructive shrink-0",
Attributes: templ.Attributes{
"hx-delete": fmt.Sprintf("/app/spaces/%s/lists/%s/items/%s", spaceID, item.ListID, item.ID),
"hx-swap": "none",
"_": fmt.Sprintf("on htmx:afterRequest send refreshItems to #list-items-%s", item.ListID),
},
}) {
@icon.X(icon.Props{Size: 14})
</button>
}
</div>
}
@ -219,12 +228,16 @@ templ CardItemDetail(spaceID string, item *model.ListItem) {
templ ListNameHeader(spaceID string, list *model.ShoppingList) {
<div id="list-name-header" class="flex items-center gap-2 group">
<h1 class="text-2xl font-bold">{ list.Name }</h1>
<button
class="text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-opacity"
_="on click toggle .hidden on #list-name-header then toggle .hidden on #list-name-edit then focus() the first <input/> in #list-name-edit"
>
@button.Button(button.Props{
Variant: button.VariantGhost,
Size: button.SizeIcon,
Class: "size-7 text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-opacity",
Attributes: templ.Attributes{
"_": "on click toggle .hidden on #list-name-header then toggle .hidden on #list-name-edit then focus() the first <input/> in #list-name-edit",
},
}) {
@icon.Pencil(icon.Props{Size: 16})
</button>
}
</div>
<form
id="list-name-edit"
@ -243,16 +256,17 @@ templ ListNameHeader(spaceID string, list *model.ShoppingList) {
"required": "true",
},
})
<button type="submit" class="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 bg-primary text-primary-foreground hover:bg-primary/90">
@button.Submit() {
Save
</button>
<button
type="button"
class="inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border hover:bg-muted"
_="on click toggle .hidden on #list-name-edit then toggle .hidden on #list-name-header"
>
}
@button.Button(button.Props{
Variant: button.VariantOutline,
Attributes: templ.Attributes{
"_": "on click toggle .hidden on #list-name-edit then toggle .hidden on #list-name-header",
},
}) {
Cancel
</button>
}
</form>
}
@ -270,13 +284,17 @@ templ ItemDetail(spaceID string, item *model.ListItem) {
},
})
<span class={ templ.KV("line-through text-muted-foreground", item.IsChecked) }>{ item.Name }</span>
<button
hx-delete={ fmt.Sprintf("/app/spaces/%s/lists/%s/items/%s", spaceID, item.ListID, item.ID) }
hx-target={ "#item-" + item.ID }
hx-swap="outerHTML"
class="ml-auto btn btn-xs btn-ghost"
>
&times;
</button>
@button.Button(button.Props{
Variant: button.VariantGhost,
Size: button.SizeIcon,
Class: "ml-auto size-7",
Attributes: templ.Attributes{
"hx-delete": fmt.Sprintf("/app/spaces/%s/lists/%s/items/%s", spaceID, item.ListID, item.ID),
"hx-target": "#item-" + item.ID,
"hx-swap": "outerHTML",
},
}) {
@icon.X(icon.Props{Size: 14})
}
</div>
}

View file

@ -11,6 +11,7 @@ import "git.juancwu.dev/juancwu/budgit/internal/ui/components/toast"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/calendar"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/datepicker"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/progress"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/selectbox"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/tagsinput"
import "fmt"
import "time"
@ -51,6 +52,7 @@ templ Base(props ...SEOProps) {
@datepicker.Script()
@progress.Script()
@tagsinput.Script()
@selectbox.Script()
// Site-wide enhancements
@themeScript()
// Must run before body to prevent flash

View file

@ -11,6 +11,7 @@ import (
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/label"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/radio"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/selectbox"
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
)
@ -178,15 +179,21 @@ templ AddBudgetForm(spaceID string, tags []*model.Tag) {
@csrf.Token()
// Tag selector
<div>
@label.Label(label.Props{For: "budget-tag"}) {
@label.Label(label.Props{}) {
Tag
}
<select name="tag_id" id="budget-tag" class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background" required>
<option value="" disabled selected>Select a tag...</option>
for _, t := range tags {
<option value={ t.ID }>{ t.Name }</option>
@selectbox.SelectBox(selectbox.Props{ID: "budget-tag"}) {
@selectbox.Trigger(selectbox.TriggerProps{Name: "tag_id"}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select a tag..."})
}
</select>
@selectbox.Content() {
for _, t := range tags {
@selectbox.Item(selectbox.ItemProps{Value: t.ID}) {
{ t.Name }
}
}
}
}
</div>
// Amount
<div>
@ -280,14 +287,21 @@ templ EditBudgetForm(spaceID string, b *model.BudgetWithSpent, tags []*model.Tag
@csrf.Token()
// Tag selector
<div>
@label.Label(label.Props{For: "edit-budget-tag-" + b.ID}) {
@label.Label(label.Props{}) {
Tag
}
<select name="tag_id" id={ "edit-budget-tag-" + b.ID } class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background" required>
for _, t := range tags {
<option value={ t.ID } selected?={ t.ID == b.TagID }>{ t.Name }</option>
@selectbox.SelectBox(selectbox.Props{ID: "edit-budget-tag-" + b.ID}) {
@selectbox.Trigger(selectbox.TriggerProps{Name: "tag_id"}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select a tag..."})
}
</select>
@selectbox.Content() {
for _, t := range tags {
@selectbox.Item(selectbox.ItemProps{Value: t.ID, Selected: t.ID == b.TagID}) {
{ t.Name }
}
}
}
}
</div>
// Amount
<div>

View file

@ -3,6 +3,7 @@ package pages
import (
"fmt"
"git.juancwu.dev/juancwu/budgit/internal/model"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/badge"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/dialog"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/expense"
@ -74,7 +75,9 @@ templ SpaceOverviewPage(space *model.Space, lists []*model.ShoppingList, tags []
if len(tags) > 0 {
<div class="flex flex-wrap gap-2">
for _, tag := range tags {
<span class="bg-secondary text-secondary-foreground rounded-full px-3 py-1 text-sm">{ tag.Name }</span>
@badge.Badge(badge.Props{Variant: badge.VariantSecondary}) {
{ tag.Name }
}
}
</div>
} else {