332 lines
7.3 KiB
Text
332 lines
7.3 KiB
Text
// templui component dialog - version: v1.2.0 installed by templui v1.2.0
|
|
// 📚 Documentation: https://templui.io/docs/components/dialog
|
|
package dialog
|
|
|
|
import (
|
|
"context"
|
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
|
|
"git.juancwu.dev/juancwu/budgit/internal/utils"
|
|
)
|
|
|
|
type contextKey string
|
|
|
|
const (
|
|
instanceKey contextKey = "dialogInstance"
|
|
openKey contextKey = "dialogOpen"
|
|
)
|
|
|
|
type Props struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
DisableClickAway bool
|
|
DisableESC bool
|
|
Open bool
|
|
}
|
|
|
|
type TriggerProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
For string // Reference to a specific dialog ID (for external triggers)
|
|
}
|
|
|
|
type ContentProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
HideCloseButton bool
|
|
Open bool // Initial open state for standalone usage (when no context)
|
|
DisableAutoFocus bool
|
|
}
|
|
|
|
type CloseProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
For string // ID of the dialog to close (optional, defaults to closest dialog)
|
|
}
|
|
|
|
type HeaderProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type FooterProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type TitleProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type DescriptionProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
templ Dialog(props ...Props) {
|
|
{{ var p Props }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
{{ instanceID := p.ID }}
|
|
if instanceID == "" {
|
|
{{ instanceID = utils.RandomID() }}
|
|
}
|
|
{{ ctx = context.WithValue(ctx, instanceKey, instanceID) }}
|
|
{{ ctx = context.WithValue(ctx, openKey, p.Open) }}
|
|
<div
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
data-tui-dialog
|
|
data-dialog-instance={ instanceID }
|
|
if p.DisableClickAway {
|
|
data-tui-dialog-disable-click-away="true"
|
|
}
|
|
if p.DisableESC {
|
|
data-tui-dialog-disable-esc="true"
|
|
}
|
|
class={ utils.TwMerge("", p.Class) }
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ Trigger(props ...TriggerProps) {
|
|
{{ var p TriggerProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
{{ instanceID := "" }}
|
|
// Explicit For prop takes priority over inherited context
|
|
if p.For != "" {
|
|
{{ instanceID = p.For }}
|
|
} else if val := ctx.Value(instanceKey); val != nil {
|
|
{{ instanceID = val.(string) }}
|
|
}
|
|
<span
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
data-tui-dialog-trigger={ instanceID }
|
|
data-dialog-instance={ instanceID }
|
|
data-tui-dialog-trigger-open="false"
|
|
class={ utils.TwMerge("contents", p.Class) }
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</span>
|
|
}
|
|
|
|
templ Content(props ...ContentProps) {
|
|
{{ var p ContentProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
// Start with prop values as defaults
|
|
{{ instanceID := p.ID }}
|
|
{{ open := p.Open }}
|
|
// Override with context values if available
|
|
if val := ctx.Value(instanceKey); val != nil {
|
|
{{ instanceID = val.(string) }}
|
|
}
|
|
if val := ctx.Value(openKey); val != nil {
|
|
{{ open = val.(bool) }}
|
|
}
|
|
// Apply defaults if still empty
|
|
if instanceID == "" {
|
|
{{ instanceID = utils.RandomID() }}
|
|
}
|
|
<!-- Overlay -->
|
|
<div
|
|
class={ utils.TwMerge(
|
|
"fixed inset-0 z-50 bg-black/50",
|
|
"transition-opacity duration-300",
|
|
"data-[tui-dialog-open=false]:opacity-0",
|
|
"data-[tui-dialog-open=true]:opacity-100",
|
|
"data-[tui-dialog-open=false]:pointer-events-none",
|
|
"data-[tui-dialog-open=true]:pointer-events-auto",
|
|
"data-[tui-dialog-hidden=true]:!hidden",
|
|
) }
|
|
data-tui-dialog-backdrop
|
|
data-dialog-instance={ instanceID }
|
|
if open {
|
|
data-tui-dialog-open="true"
|
|
} else {
|
|
data-tui-dialog-open="false"
|
|
data-tui-dialog-hidden="true"
|
|
}
|
|
></div>
|
|
<!-- Content -->
|
|
<div
|
|
class={
|
|
utils.TwMerge(
|
|
// Base positioning
|
|
"fixed z-50 left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%]",
|
|
// Style
|
|
"bg-background rounded-lg border shadow-lg",
|
|
// Layout
|
|
"grid gap-4 p-6",
|
|
// Size
|
|
"w-full max-w-[calc(100%-2rem)] sm:max-w-lg",
|
|
// Transitions
|
|
"transition-all duration-200",
|
|
// Scale animation
|
|
"data-[tui-dialog-open=false]:scale-95",
|
|
"data-[tui-dialog-open=true]:scale-100",
|
|
// Opacity
|
|
"data-[tui-dialog-open=false]:opacity-0",
|
|
"data-[tui-dialog-open=true]:opacity-100",
|
|
// Pointer events
|
|
"data-[tui-dialog-open=false]:pointer-events-none",
|
|
"data-[tui-dialog-open=true]:pointer-events-auto",
|
|
// Hidden state
|
|
"data-[tui-dialog-hidden=true]:!hidden",
|
|
p.Class,
|
|
),
|
|
}
|
|
data-tui-dialog-content
|
|
data-dialog-instance={ instanceID }
|
|
if p.DisableAutoFocus {
|
|
data-tui-dialog-disable-autofocus="true"
|
|
}
|
|
if open {
|
|
data-tui-dialog-open="true"
|
|
} else {
|
|
data-tui-dialog-open="false"
|
|
data-tui-dialog-hidden="true"
|
|
}
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
if !p.HideCloseButton {
|
|
<button
|
|
class={ utils.TwMerge(
|
|
// Positioning
|
|
"absolute top-4 right-4",
|
|
// Style
|
|
"rounded-xs opacity-70",
|
|
// Interactions
|
|
"transition-opacity hover:opacity-100",
|
|
// Focus states
|
|
"focus:outline-none focus:ring-2",
|
|
"focus:ring-ring focus:ring-offset-2",
|
|
"ring-offset-background",
|
|
// Hover/Data states
|
|
"data-[tui-dialog-open=true]:bg-accent",
|
|
"data-[tui-dialog-open=true]:text-muted-foreground",
|
|
// Disabled state
|
|
"disabled:pointer-events-none",
|
|
// Icon styles
|
|
"[&_svg]:pointer-events-none",
|
|
"[&_svg]:shrink-0",
|
|
"[&_svg:not([class*='size-'])]:size-4",
|
|
) }
|
|
data-tui-dialog-close={ instanceID }
|
|
aria-label="Close"
|
|
type="button"
|
|
>
|
|
@icon.X()
|
|
<span class="sr-only">Close</span>
|
|
</button>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
templ Close(props ...CloseProps) {
|
|
{{ var p CloseProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<span
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
if p.For != "" {
|
|
data-tui-dialog-close={ p.For }
|
|
} else {
|
|
data-tui-dialog-close
|
|
}
|
|
class={ utils.TwMerge("contents cursor-pointer", p.Class) }
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</span>
|
|
}
|
|
|
|
templ Header(props ...HeaderProps) {
|
|
{{ var p HeaderProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<div
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("flex flex-col gap-2 text-center sm:text-left", p.Class) }
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ Footer(props ...FooterProps) {
|
|
{{ var p FooterProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<div
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", p.Class) }
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ Title(props ...TitleProps) {
|
|
{{ var p TitleProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<h2
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("text-lg leading-none font-semibold", p.Class) }
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</h2>
|
|
}
|
|
|
|
templ Description(props ...DescriptionProps) {
|
|
{{ var p DescriptionProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<p
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("text-muted-foreground text-sm", p.Class) }
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</p>
|
|
}
|
|
|
|
templ Script() {
|
|
<script defer nonce={ templ.GetNonce(ctx) } src={ utils.ScriptURL("/assets/js/dialog.min.js") }></script>
|
|
}
|