chore: update templ and templui

This commit is contained in:
juancwu 2026-04-12 16:07:06 +00:00
commit 61eaa268ab
89 changed files with 25776 additions and 8231 deletions

View file

@ -1,10 +1,8 @@
// templui component selectbox - version: v1.2.0 installed by templui v1.2.0
// templui component selectbox - version: v1.9.5 installed by templui v1.9.5
// 📚 Documentation: https://templui.io/docs/components/select-box
package selectbox
import (
"context"
"fmt"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
@ -13,10 +11,6 @@ import (
"strconv"
)
type contextKey string
var contentIDKey contextKey = "contentID"
type Props struct {
ID string
Class string
@ -80,170 +74,18 @@ templ SelectBox(props ...Props) {
if len(props) > 0 {
p = props[0]
}
wrapperID := p.ID
if wrapperID == "" {
wrapperID = utils.RandomID()
}
contentID := fmt.Sprintf("%s-content", wrapperID)
ctx = context.WithValue(ctx, contentIDKey, contentID)
}}
<div
id={ wrapperID }
class={ utils.TwMerge("select-container w-full relative", p.Class) }
{ p.Attributes... }
>
{ children... }
</div>
}
templ Trigger(props ...TriggerProps) {
{{
var p TriggerProps
if len(props) > 0 {
p = props[0]
}
contentID, ok := ctx.Value(contentIDKey).(string)
if !ok {
contentID = "fallback-select-content-id"
}
if p.ShowPills {
p.Multiple = true
}
}}
@popover.Trigger(popover.TriggerProps{
For: contentID,
TriggerType: popover.TriggerTypeClick,
}) {
@button.Button(button.Props{
ID: p.ID,
Type: "button",
Variant: button.VariantOutline,
Class: utils.TwMerge(
// Required class for JavaScript
"select-trigger",
// Base styles matching input
"w-full h-9 px-3 py-1 text-base md:text-sm",
"flex items-center justify-between",
"rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] outline-none",
// Dark mode background
"dark:bg-input/30",
// Selection styles
"selection:bg-primary selection:text-primary-foreground",
// Focus styles
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
// Error/Invalid styles
"aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40",
utils.If(p.HasError, "border-destructive ring-destructive/20 dark:ring-destructive/40"),
p.Class,
),
Disabled: p.Disabled,
Attributes: utils.MergeAttributes(
templ.Attributes{
"data-tui-selectbox-content-id": contentID,
"data-tui-selectbox-multiple": strconv.FormatBool(p.Multiple),
"data-tui-selectbox-show-pills": strconv.FormatBool(p.ShowPills),
"data-tui-selectbox-selected-count-text": p.SelectedCountText,
"tabindex": "0",
"aria-invalid": utils.If(p.HasError, "true"),
},
),
}) {
<input
type="hidden"
if p.Name != "" {
name={ p.Name }
}
if p.Form != "" {
form={ p.Form }
}
data-tui-selectbox-hidden-input
{ p.Attributes... }
/>
{ children... }
<span class="pointer-events-none ml-1">
@icon.ChevronDown(icon.Props{
Size: 16,
Class: "text-muted-foreground",
})
</span>
}
}
}
templ Value(props ...ValueProps) {
{{ var p ValueProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("block truncate select-value text-muted-foreground", p.Class) }
if p.Placeholder != "" {
data-tui-selectbox-placeholder={ p.Placeholder }
}
class={ utils.TwMerge("select-container w-full relative", p.Class) }
{ p.Attributes... }
>
if p.Placeholder != "" {
{ p.Placeholder }
}
{ children... }
</span>
}
templ Content(props ...ContentProps) {
{{
var p ContentProps
if len(props) > 0 {
p = props[0]
}
contentID, ok := ctx.Value(contentIDKey).(string)
if !ok {
contentID = "fallback-select-content-id"
}
}}
@popover.Content(popover.ContentProps{
ID: contentID,
Placement: popover.PlacementBottomStart,
Offset: 4,
MatchWidth: true,
DisableESC: !p.NoSearch,
Class: utils.TwMerge(
"p-1 select-content z-50 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
"min-w-[var(--popover-trigger-width)] w-[var(--popover-trigger-width)]",
p.Class,
),
Attributes: utils.MergeAttributes(
templ.Attributes{
"role": "listbox",
"tabindex": "-1",
},
p.Attributes,
),
Exclusive: true,
}) {
if !p.NoSearch {
<div class="sticky top-0 bg-popover p-1">
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground z-10 pointer-events-none">
@icon.Search(icon.Props{Size: 16})
</span>
@input.Input(input.Props{
Type: input.TypeSearch,
Class: "pl-8",
Placeholder: utils.IfElse(p.SearchPlaceholder != "", p.SearchPlaceholder, "Search..."),
Attributes: templ.Attributes{
"data-tui-selectbox-search": "",
},
})
</div>
</div>
}
<div class="max-h-[300px] overflow-y-auto">
@popover.Root() {
{ children... }
</div>
}
}
</div>
}
templ Group(props ...GroupProps) {
@ -290,10 +132,10 @@ templ Item(props ...ItemProps) {
}
class={
utils.TwMerge(
"select-item relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-light outline-none",
"select-item group relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-light outline-none",
"hover:bg-accent hover:text-accent-foreground",
"focus:bg-accent focus:text-accent-foreground",
utils.If(p.Selected, "bg-accent text-accent-foreground"),
"focus-visible:bg-accent focus-visible:text-accent-foreground",
"data-[tui-selectbox-selected=true]:bg-accent data-[tui-selectbox-selected=true]:text-accent-foreground",
utils.If(p.Disabled, "pointer-events-none opacity-50"),
p.Class,
),
@ -311,16 +153,163 @@ templ Item(props ...ItemProps) {
<span
class={
utils.TwMerge(
"select-check absolute right-2 flex h-3.5 w-3.5 items-center justify-center",
utils.IfElse(p.Selected, "opacity-100", "opacity-0"),
"select-check absolute right-2 flex h-3.5 w-3.5 items-center justify-center opacity-0",
"group-data-[tui-selectbox-selected=true]:opacity-100",
),
}
>
@icon.Check(icon.Props{Size: 16})
@icon.Check(icon.Props{Class: "size-4"})
</span>
</div>
}
templ Script() {
<script defer nonce={ templ.GetNonce(ctx) } src={ utils.ScriptURL("/assets/js/selectbox.min.js") }></script>
templ Trigger(props ...TriggerProps) {
{{
var p TriggerProps
if len(props) > 0 {
p = props[0]
}
if p.ShowPills {
p.Multiple = true
}
}}
@popover.Trigger(popover.TriggerProps{
TriggerType: popover.TriggerTypeClick,
}) {
@button.Button(button.Props{
ID: p.ID,
Type: "button",
Variant: button.VariantOutline,
Class: utils.TwMerge(
// Required class for JavaScript
"select-trigger",
// Base styles matching input
"w-full h-9 px-3 py-1 text-base md:text-sm",
"flex items-center justify-between",
"rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] outline-none",
// Dark mode background
"dark:bg-input/30",
// Selection styles
"selection:bg-primary selection:text-primary-foreground",
// Focus styles
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
// Error/Invalid styles
"aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40",
utils.If(p.HasError, "border-destructive ring-destructive/20 dark:ring-destructive/40"),
p.Class,
),
Disabled: p.Disabled,
Attributes: utils.MergeAttributes(
templ.Attributes{
"data-tui-selectbox-multiple": strconv.FormatBool(p.Multiple),
"data-tui-selectbox-show-pills": strconv.FormatBool(p.ShowPills),
"data-tui-selectbox-selected-count-text": p.SelectedCountText,
"tabindex": "0",
"aria-invalid": utils.If(p.HasError, "true"),
},
),
}) {
<input
type="hidden"
if p.Name != "" {
name={ p.Name }
}
if p.Form != "" {
form={ p.Form }
}
data-tui-selectbox-hidden-input
{ p.Attributes... }
/>
{ children... }
<span
class="ml-1 hidden cursor-pointer text-muted-foreground hover:text-foreground"
data-tui-selectbox-clear-trigger
aria-label="Clear selection"
role="button"
>
@icon.CircleX(icon.Props{Class: "size-3.5"})
</span>
<span class="pointer-events-none ml-1" data-tui-selectbox-chevron>
@icon.ChevronDown(icon.Props{Class: "size-4 text-muted-foreground"})
</span>
}
}
}
templ Value(props ...ValueProps) {
{{ var p ValueProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
if p.ID != "" {
id={ p.ID }
}
class={ utils.TwMerge("block truncate select-value text-muted-foreground", p.Class) }
if p.Placeholder != "" {
data-tui-selectbox-placeholder={ p.Placeholder }
}
{ p.Attributes... }
>
if p.Placeholder != "" {
{ p.Placeholder }
}
{ children... }
</span>
}
templ Content(props ...ContentProps) {
{{ var p ContentProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
@popover.Content(popover.ContentProps{
Placement: popover.PlacementBottomStart,
Offset: 4,
DisableESC: !p.NoSearch,
Class: utils.TwMerge(
"p-1 select-content z-50 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
p.Class,
),
Attributes: utils.MergeAttributes(
templ.Attributes{
"role": "listbox",
"tabindex": "-1",
"data-tui-selectbox-content": "true",
},
p.Attributes,
),
Exclusive: true,
}) {
if !p.NoSearch {
<div class="sticky top-0 bg-popover p-1">
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground z-10 pointer-events-none">
@icon.Search(icon.Props{Class: "size-4"})
</span>
@input.Input(input.Props{
Type: input.TypeSearch,
Class: "pl-8",
Placeholder: utils.IfElse(p.SearchPlaceholder != "", p.SearchPlaceholder, "Search..."),
Attributes: templ.Attributes{
"data-tui-selectbox-search": "",
},
})
</div>
</div>
}
<div class="max-h-[300px] overflow-y-auto">
{ children... }
</div>
}
}
var scriptOnce = templ.NewOnceHandle()
templ Script() {
@scriptOnce.Once() {
@input.Script()
@popover.Script()
@utils.ComponentScript("selectbox")
}
}