budgit/internal/ui/components/sheet/sheet.templ
2026-01-18 22:23:21 +00:00

318 lines
7.9 KiB
Text

// templui component sheet - version: v1.2.0 installed by templui v1.2.0
// 📚 Documentation: https://templui.io/docs/components/sheet
package sheet
import (
"context"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/dialog"
"git.juancwu.dev/juancwu/budgit/internal/utils"
)
type contextKey string
const (
sideKey contextKey = "sheetSide"
)
type Side string
const (
SideTop Side = "top"
SideRight Side = "right"
SideBottom Side = "bottom"
SideLeft Side = "left"
)
type Props struct {
ID string
Class string
Attributes templ.Attributes
Side Side
Open bool
DisableClickAway bool
DisableESC bool
}
type TriggerProps struct {
ID string
Class string
Attributes templ.Attributes
For string // Reference to a specific sheet ID (for external triggers)
}
type ContentProps struct {
ID string
Class string
Attributes templ.Attributes
HideCloseButton bool
Side Side //
Open bool // Initial open state for standalone usage
}
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
}
type CloseProps struct {
ID string
Class string
Attributes templ.Attributes
For string
}
templ Sheet(props ...Props) {
{{ var p Props }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.Side == "" {
{{ p.Side = SideRight }}
}
// Pass the Side through context to child components
{{ ctx = context.WithValue(ctx, sideKey, p.Side) }}
// Sheet uses Dialog internally with sheet-specific attributes
@dialog.Dialog(dialog.Props{
ID: p.ID,
Open: p.Open,
DisableClickAway: p.DisableClickAway,
DisableESC: p.DisableESC,
Class: p.Class,
Attributes: utils.MergeAttributes(
templ.Attributes{
"data-tui-sheet": "true",
"data-tui-sheet-side": string(p.Side),
},
p.Attributes,
),
}) {
{ children... }
}
}
templ Trigger(props ...TriggerProps) {
{{ var p TriggerProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Sheet trigger is just a Dialog trigger
@dialog.Trigger(dialog.TriggerProps{
ID: p.ID,
For: p.For,
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
}
templ Content(props ...ContentProps) {
{{ var p ContentProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Get Side from context if not explicitly provided
if p.Side == "" {
if val := ctx.Value(sideKey); val != nil {
{{ p.Side = val.(Side) }}
} else {
{{ p.Side = SideRight }}
}
}
// Sheet content uses Dialog content with sheet-specific styles
@dialog.Content(dialog.ContentProps{
ID: p.ID,
Open: p.Open,
HideCloseButton: p.HideCloseButton,
Class: utils.TwMerge(
// First apply side-specific positioning and animations
getSideClasses(p.Side),
// Default gap matching shadcn (no padding in content)
"gap-4 !p-0", // Remove Dialog's p-6 padding
// Override Dialog styles
"!scale-100", // Reset Dialog's scale animation
"!rounded-none", // Remove dialog rounded corners
"!opacity-100", // Keep fully opaque - no fade, only slide
// Remove pointer-events control during animation
"!pointer-events-auto data-[tui-dialog-hidden=true]:!pointer-events-none",
// User-provided classes last
p.Class,
),
Attributes: utils.MergeAttributes(
templ.Attributes{
"data-tui-sheet-content": "true",
"data-tui-sheet-side": string(p.Side),
},
p.Attributes,
),
}) {
{ children... }
}
}
templ Header(props ...HeaderProps) {
{{ var p HeaderProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Sheet header uses Dialog header but overrides styles
@dialog.Header(dialog.HeaderProps{
ID: p.ID,
Class: utils.TwMerge("gap-1.5 p-4 text-left", p.Class),
Attributes: p.Attributes,
}) {
{ children... }
}
}
templ Title(props ...TitleProps) {
{{ var p TitleProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Sheet title uses Dialog title but overrides styles
@dialog.Title(dialog.TitleProps{
ID: p.ID,
Class: utils.TwMerge("text-base leading-normal", p.Class),
Attributes: p.Attributes,
}) {
{ children... }
}
}
templ Description(props ...DescriptionProps) {
{{ var p DescriptionProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Sheet description uses Dialog description
@dialog.Description(dialog.DescriptionProps{
ID: p.ID,
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
}
templ Footer(props ...FooterProps) {
{{ var p FooterProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Sheet footer uses Dialog footer but overrides styles
@dialog.Footer(dialog.FooterProps{
ID: p.ID,
Class: utils.TwMerge("mt-auto flex flex-col gap-2 p-4", p.Class),
Attributes: p.Attributes,
}) {
{ children... }
}
}
templ Close(props ...CloseProps) {
{{ var p CloseProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
// Sheet close uses Dialog close
@dialog.Close(dialog.CloseProps{
ID: p.ID,
For: p.For,
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
}
func getSideClasses(side Side) string {
// Base classes for all sheets - matching shadcn
// Duration varies: 300ms when closing, 500ms when opening
// Use !transition-transform to override Dialog's transition-all
baseClasses := "fixed z-50 flex flex-col bg-background shadow-lg !transition-transform ease-in-out " +
"data-[tui-dialog-open=false]:duration-300 data-[tui-dialog-open=true]:duration-500 "
switch side {
case SideRight:
return baseClasses +
// Positioning
"!inset-y-0 !right-0 !left-auto !top-auto " +
// Size
"h-full w-3/4 sm:max-w-sm " +
// Border
"border-l border-t-0 border-r-0 border-b-0 " +
// Reset Dialog transforms
"!translate-y-0 " +
// Slide animation
"data-[tui-dialog-open=false]:!translate-x-full " +
"data-[tui-dialog-open=true]:!translate-x-0"
case SideLeft:
return baseClasses +
// Positioning
"!inset-y-0 !left-0 !right-auto !top-auto " +
// Size
"h-full w-3/4 sm:max-w-sm " +
// Border
"border-r border-t-0 border-l-0 border-b-0 " +
// Reset Dialog transforms
"!translate-y-0 " +
// Slide animation
"data-[tui-dialog-open=false]:!-translate-x-full " +
"data-[tui-dialog-open=true]:!translate-x-0"
case SideTop:
return baseClasses +
// Positioning - full width at top
"!inset-x-0 !top-0 !bottom-auto !left-0 !right-0 " +
// Size - full width, auto height
"!w-full !max-w-full h-auto " +
// Border
"border-b border-t-0 border-l-0 border-r-0 " +
// Reset Dialog transforms - IMPORTANT: reset the centering from Dialog
"!translate-x-0 !translate-y-0 " +
// Slide animation - top slides up when closing
"data-[tui-dialog-open=false]:!-translate-y-full " +
"data-[tui-dialog-open=true]:!translate-y-0"
case SideBottom:
return baseClasses +
// Positioning - full width at bottom
"!inset-x-0 !bottom-0 !top-auto !left-0 !right-0 " +
// Size - full width, auto height
"!w-full !max-w-full h-auto " +
// Border
"border-t border-b-0 border-l-0 border-r-0 " +
// Reset Dialog transforms - IMPORTANT: reset the centering from Dialog
"!translate-x-0 !translate-y-0 " +
// Slide animation
"data-[tui-dialog-open=false]:!translate-y-full " +
"data-[tui-dialog-open=true]:!translate-y-0"
default:
return baseClasses +
// Default to right side
"!inset-y-0 !right-0 !left-auto !top-auto " +
"h-full w-3/4 " +
"border-l border-t-0 border-r-0 border-b-0 " +
"!translate-y-0 " +
"data-[tui-dialog-open=false]:!translate-x-full " +
"data-[tui-dialog-open=true]:!translate-x-0"
}
}