753 lines
21 KiB
Text
753 lines
21 KiB
Text
// templui component sidebar - version: v1.2.0 installed by templui v1.2.0
|
|
// 📚 Documentation: https://templui.io/docs/components/sidebar
|
|
package sidebar
|
|
|
|
import "context"
|
|
import "git.juancwu.dev/juancwu/budgit/internal/utils"
|
|
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/icon"
|
|
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
|
|
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/sheet"
|
|
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/tooltip"
|
|
|
|
type contextKey string
|
|
|
|
const sidebarIDKey contextKey = "sidebar-id"
|
|
|
|
type Side string
|
|
|
|
const (
|
|
SideLeft Side = "left" // default
|
|
SideRight Side = "right"
|
|
)
|
|
|
|
type Variant string
|
|
|
|
const (
|
|
VariantSidebar Variant = "sidebar" // default
|
|
VariantFloating Variant = "floating"
|
|
VariantInset Variant = "inset"
|
|
)
|
|
|
|
type Collapsible string
|
|
|
|
const (
|
|
CollapsibleOffcanvas Collapsible = "offcanvas" // default
|
|
CollapsibleIcon Collapsible = "icon"
|
|
CollapsibleNone Collapsible = "none"
|
|
)
|
|
|
|
type Props struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
Side Side // default: "left"
|
|
Variant Variant // default: "sidebar"
|
|
Collapsible Collapsible // default: "offcanvas"
|
|
Collapsed bool // default: false (sidebar open)
|
|
KeyboardShortcut string // default: "b"
|
|
}
|
|
|
|
type TriggerProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
Target string // Target sidebar ID to toggle
|
|
}
|
|
|
|
type HeaderProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type ContentProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type FooterProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type InsetProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type GroupProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type GroupLabelProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type MenuProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type MenuItemProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type MenuButtonSize string
|
|
|
|
const (
|
|
MenuButtonSizeDefault MenuButtonSize = "default" // default
|
|
MenuButtonSizeSm MenuButtonSize = "sm"
|
|
MenuButtonSizeLg MenuButtonSize = "lg"
|
|
)
|
|
|
|
type MenuButtonProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
Href string
|
|
IsActive bool
|
|
Size MenuButtonSize // default: "default"
|
|
Tooltip string // Tooltip text to show when sidebar is collapsed
|
|
}
|
|
|
|
type MenuBadgeProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type MenuSubProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type MenuSubItemProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type MenuSubButtonProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
Href string
|
|
IsActive bool
|
|
}
|
|
|
|
type SeparatorProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
type LayoutProps struct {
|
|
ID string
|
|
Class string
|
|
Attributes templ.Attributes
|
|
}
|
|
|
|
templ Layout(props ...LayoutProps) {
|
|
{{ var p LayoutProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
// Generate ID for context (children components can use it for targeting)
|
|
{{ var sidebarId string = p.ID }}
|
|
if sidebarId == "" {
|
|
{{ sidebarId = utils.RandomID() }}
|
|
}
|
|
// Set sidebar ID in context for children to access
|
|
{{ ctx = context.WithValue(ctx, sidebarIDKey, sidebarId) }}
|
|
<!-- Layout Container - No ID needed here -->
|
|
<div
|
|
class={ utils.TwMerge(
|
|
"flex min-h-svh relative",
|
|
"has-[[data-tui-sidebar-variant=inset]]:bg-sidebar",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar-layout
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ Sidebar(props ...Props) {
|
|
{{ var p Props }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
// Get sidebar ID from context or use provided/generate new
|
|
if p.ID == "" {
|
|
if ctxId := ctx.Value(sidebarIDKey); ctxId != nil {
|
|
{{ p.ID = ctxId.(string) }}
|
|
} else {
|
|
{{ p.ID = utils.RandomID() }}
|
|
}
|
|
}
|
|
if p.Side == "" {
|
|
{{ p.Side = SideLeft }}
|
|
}
|
|
if p.Variant == "" {
|
|
{{ p.Variant = VariantSidebar }}
|
|
}
|
|
if p.Collapsible == "" {
|
|
{{ p.Collapsible = CollapsibleOffcanvas }}
|
|
}
|
|
if p.KeyboardShortcut == "" {
|
|
{{ p.KeyboardShortcut = "b" }}
|
|
}
|
|
// Use the sidebar's ID for mobile sheet and trigger targeting
|
|
{{ var sidebarId string = p.ID }}
|
|
{{ sidebarState := "expanded" }}
|
|
if p.Collapsed {
|
|
{{ sidebarState = "collapsed" }}
|
|
}
|
|
<!-- Mobile: Sheet Component for < 768px -->
|
|
{{ var sheetSide sheet.Side }}
|
|
if p.Side == SideRight {
|
|
{{ sheetSide = sheet.SideRight }}
|
|
} else {
|
|
{{ sheetSide = sheet.SideLeft }}
|
|
}
|
|
<!-- Mobile sheet wrapper without content -->
|
|
@sheet.Sheet(sheet.Props{
|
|
ID: sidebarId + "-mobile",
|
|
Side: sheetSide,
|
|
Open: false,
|
|
}) {
|
|
@sheet.Content(sheet.ContentProps{
|
|
Class: "md:hidden bg-sidebar text-sidebar-foreground !p-0 !gap-0 flex flex-col h-full",
|
|
HideCloseButton: true,
|
|
}) {
|
|
<!-- Mobile content wrapper: visible only on mobile via JS -->
|
|
<div class="sidebar-mobile-portal flex h-full flex-col" data-tui-sidebar-mobile-portal={ sidebarId }></div>
|
|
}
|
|
}
|
|
<!-- Desktop: Sidebar for >= 768px -->
|
|
<div
|
|
class={ utils.TwMerge(
|
|
"group peer hidden md:block",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar-state={ sidebarState }
|
|
data-tui-sidebar-collapsible={ string(p.Collapsible) }
|
|
data-tui-sidebar-variant={ string(p.Variant) }
|
|
data-tui-sidebar-side={ string(p.Side) }
|
|
data-tui-sidebar-wrapper
|
|
data-tui-sidebar-id={ p.ID }
|
|
data-tui-sidebar-keyboard-shortcut={ p.KeyboardShortcut }
|
|
{ utils.MergeAttributes(
|
|
templ.Attributes{"style": "--sidebar-width:16rem"},
|
|
p.Attributes,
|
|
)... }
|
|
>
|
|
<!-- Gap element for document flow -->
|
|
<div
|
|
class={ utils.TwMerge(
|
|
"relative bg-transparent transition-[width] duration-200 ease-linear",
|
|
"w-[var(--sidebar-width,16rem)]",
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=offcanvas]:w-0",
|
|
"group-data-[tui-sidebar-side=right]:rotate-180",
|
|
// Add padding for floating/inset variants when collapsed to icon mode
|
|
"group-data-[tui-sidebar-variant=floating]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:w-[calc(3rem+theme(spacing.4))]",
|
|
"group-data-[tui-sidebar-variant=inset]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:w-[calc(3rem+theme(spacing.4))]",
|
|
"group-data-[tui-sidebar-variant=sidebar]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:w-12",
|
|
) }
|
|
></div>
|
|
<!-- Sidebar Container -->
|
|
<aside
|
|
id={ p.ID }
|
|
class={ utils.TwMerge(
|
|
"fixed inset-y-0 z-10 hidden h-svh transition-transform duration-200 ease-linear md:flex",
|
|
"w-[var(--sidebar-width,16rem)]",
|
|
// Side positioning with data attributes
|
|
"group-data-[tui-sidebar-side=right]:right-0 group-data-[tui-sidebar-side=right]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=offcanvas]:translate-x-full",
|
|
"group-data-[tui-sidebar-side=left]:left-0 group-data-[tui-sidebar-side=left]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=offcanvas]:-translate-x-full",
|
|
// Adjust padding and width for variants
|
|
"group-data-[tui-sidebar-variant=floating]:p-2 group-data-[tui-sidebar-variant=floating]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:w-[calc(3rem+(theme(spacing.4))+2px)]",
|
|
"group-data-[tui-sidebar-variant=inset]:p-2 group-data-[tui-sidebar-variant=inset]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:w-[calc(3rem+(theme(spacing.4))+2px)]",
|
|
"group-data-[tui-sidebar-variant=sidebar]:group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:w-12",
|
|
"group-data-[tui-sidebar-variant=sidebar]:group-data-[tui-sidebar-side=left]:border-r group-data-[tui-sidebar-variant=sidebar]:group-data-[tui-sidebar-side=right]:border-l",
|
|
p.Class,
|
|
) }
|
|
data-sidebar="sidebar"
|
|
>
|
|
<!-- Inner sidebar with variant-specific styling -->
|
|
<div
|
|
data-sidebar="sidebar"
|
|
class={ utils.TwMerge(
|
|
"bg-sidebar group-data-[tui-sidebar-variant=floating]:border-sidebar-border flex h-full w-full flex-col",
|
|
"group-data-[tui-sidebar-variant=floating]:rounded-lg group-data-[tui-sidebar-variant=floating]:border group-data-[tui-sidebar-variant=floating]:shadow-sm",
|
|
) }
|
|
>
|
|
<!-- Main content: rendered once, shown conditionally -->
|
|
<div data-tui-sidebar-content={ sidebarId } class="flex h-full w-full flex-col sidebar-content">
|
|
{ children... }
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
}
|
|
|
|
templ Trigger(props ...TriggerProps) {
|
|
{{ var p TriggerProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
// Get sidebar ID from: 1. Target prop, 2. Context
|
|
{{ var sidebarId string }}
|
|
if p.Target != "" {
|
|
{{ sidebarId = p.Target }}
|
|
} else if ctxId := ctx.Value(sidebarIDKey); ctxId != nil {
|
|
{{ sidebarId = ctxId.(string) }}
|
|
}
|
|
<!-- Mobile: Trigger to open the sheet created in Sidebar component -->
|
|
@sheet.Trigger(sheet.TriggerProps{
|
|
For: sidebarId + "-mobile",
|
|
Class: "md:hidden",
|
|
}) {
|
|
@button.Button(button.Props{
|
|
Size: button.SizeIcon,
|
|
Variant: button.VariantGhost,
|
|
Class: "size-7",
|
|
}) {
|
|
@icon.PanelLeft(icon.Props{Class: "size-4"})
|
|
<span class="sr-only">Toggle Sidebar</span>
|
|
}
|
|
}
|
|
<!-- Desktop: Sidebar Trigger -->
|
|
<div class="hidden md:block">
|
|
@button.Button(button.Props{
|
|
Size: button.SizeIcon,
|
|
Variant: button.VariantGhost,
|
|
Class: utils.TwMerge(
|
|
"size-7",
|
|
p.Class,
|
|
),
|
|
Attributes: utils.MergeAttributes(
|
|
templ.Attributes{
|
|
"data-tui-sidebar-trigger": true,
|
|
"data-tui-sidebar-target": sidebarId,
|
|
},
|
|
p.Attributes,
|
|
)},
|
|
) {
|
|
@icon.PanelLeft(icon.Props{Class: "size-4"})
|
|
<span class="sr-only">Toggle Sidebar</span>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
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 p-2", p.Class) }
|
|
data-tui-sidebar="header"
|
|
{ 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("mt-auto flex flex-col gap-2 p-2", p.Class) }
|
|
data-tui-sidebar="footer"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ Content(props ...ContentProps) {
|
|
{{ var p ContentProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<div
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge(
|
|
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto",
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:overflow-hidden",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="content"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ Menu(props ...MenuProps) {
|
|
{{ var p MenuProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<ul
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("flex w-full min-w-0 flex-col gap-1", p.Class) }
|
|
data-tui-sidebar="menu"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</ul>
|
|
}
|
|
|
|
templ MenuItem(props ...MenuItemProps) {
|
|
{{ var p MenuItemProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<li
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("group/menu-item relative", p.Class) }
|
|
data-tui-sidebar="menu-item"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</li>
|
|
}
|
|
|
|
templ MenuButton(props ...MenuButtonProps) {
|
|
{{ var p MenuButtonProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
if p.Size == "" {
|
|
{{ p.Size = MenuButtonSizeDefault }}
|
|
}
|
|
if p.Tooltip != "" {
|
|
{{ tooltipID := utils.RandomID() }}
|
|
// When collapsed to icon mode - show with tooltip
|
|
<div class="group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:block hidden">
|
|
@tooltip.Tooltip() {
|
|
@tooltip.Trigger(tooltip.TriggerProps{
|
|
For: tooltipID,
|
|
}) {
|
|
@menuButtonContent(p, "") {
|
|
{ children... }
|
|
}
|
|
}
|
|
@tooltip.Content(tooltip.ContentProps{
|
|
ID: tooltipID,
|
|
Position: tooltip.PositionRight,
|
|
HoverDelay: 200,
|
|
HoverOutDelay: 100,
|
|
}) {
|
|
{ p.Tooltip }
|
|
}
|
|
}
|
|
</div>
|
|
// When expanded - show without tooltip
|
|
<div class="group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:hidden">
|
|
@menuButtonContent(p, "") {
|
|
{ children... }
|
|
}
|
|
</div>
|
|
} else {
|
|
@menuButtonContent(p, "") {
|
|
{ children... }
|
|
}
|
|
}
|
|
}
|
|
|
|
templ menuButtonContent(p MenuButtonProps, buttonID string) {
|
|
if p.Href != "" {
|
|
<a
|
|
if buttonID != "" {
|
|
id={ buttonID }
|
|
}
|
|
href={ templ.SafeURL(p.Href) }
|
|
class={ utils.TwMerge(
|
|
"flex w-full items-center gap-2 rounded-md p-2 text-left overflow-hidden",
|
|
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
"transition-[width,height,padding]",
|
|
// Size variants with data attributes
|
|
"data-[tui-sidebar-size=sm]:h-7 data-[tui-sidebar-size=sm]:text-xs",
|
|
"data-[tui-sidebar-size=lg]:h-12 data-[tui-sidebar-size=lg]:text-sm",
|
|
"data-[tui-sidebar-size=default]:h-8 data-[tui-sidebar-size=default]:text-sm",
|
|
// Active state with data attributes
|
|
"data-[tui-sidebar-active=true]:bg-sidebar-accent data-[tui-sidebar-active=true]:text-sidebar-accent-foreground data-[tui-sidebar-active=true]:font-medium",
|
|
// Collapsed icon mode styles - matching shadcn exactly
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:!size-8",
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:!p-2",
|
|
// Override padding for lg size (avatars) in collapsed mode
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:data-[tui-sidebar-size=lg]:!p-0",
|
|
"[&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="menu-button"
|
|
data-tui-sidebar-size={ string(p.Size) }
|
|
if p.IsActive {
|
|
data-tui-sidebar-active="true"
|
|
}
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</a>
|
|
} else {
|
|
<button
|
|
if buttonID != "" {
|
|
id={ buttonID }
|
|
}
|
|
type="button"
|
|
class={ utils.TwMerge(
|
|
"flex w-full items-center gap-2 rounded-md p-2 text-left overflow-hidden",
|
|
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
"transition-[width,height,padding]",
|
|
// Size variants with data attributes
|
|
"data-[tui-sidebar-size=sm]:h-7 data-[tui-sidebar-size=sm]:text-xs",
|
|
"data-[tui-sidebar-size=lg]:h-12 data-[tui-sidebar-size=lg]:text-sm",
|
|
"data-[tui-sidebar-size=default]:h-8 data-[tui-sidebar-size=default]:text-sm",
|
|
// Active state with data attributes
|
|
"data-[tui-sidebar-active=true]:bg-sidebar-accent data-[tui-sidebar-active=true]:text-sidebar-accent-foreground data-[tui-sidebar-active=true]:font-medium",
|
|
// Collapsed icon mode styles - matching shadcn exactly
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:!size-8",
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:!p-2",
|
|
// Override padding for lg size (avatars) in collapsed mode
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:data-[tui-sidebar-size=lg]:!p-0",
|
|
"[&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="menu-button"
|
|
data-tui-sidebar-size={ string(p.Size) }
|
|
if p.IsActive {
|
|
data-tui-sidebar-active="true"
|
|
}
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</button>
|
|
}
|
|
}
|
|
|
|
templ MenuSub(props ...MenuSubProps) {
|
|
{{ var p MenuSubProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<ul
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge(
|
|
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:hidden",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="menu-sub"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</ul>
|
|
}
|
|
|
|
templ MenuSubItem(props ...MenuSubItemProps) {
|
|
{{ var p MenuSubItemProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<li
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("group/menu-sub-item relative", p.Class) }
|
|
data-tui-sidebar="menu-sub-item"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</li>
|
|
}
|
|
|
|
templ MenuSubButton(props ...MenuSubButtonProps) {
|
|
{{ var p MenuSubButtonProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
if p.Href != "" {
|
|
<a
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
href={ templ.SafeURL(p.Href) }
|
|
class={ utils.TwMerge(
|
|
"flex items-center gap-2 rounded-md px-2 py-1.5 text-sm",
|
|
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
"transition-colors",
|
|
"data-[tui-sidebar-active=true]:bg-sidebar-accent data-[tui-sidebar-active=true]:text-sidebar-accent-foreground data-[tui-sidebar-active=true]:font-medium",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="menu-sub-button"
|
|
if p.IsActive {
|
|
data-tui-sidebar-active="true"
|
|
}
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</a>
|
|
} else {
|
|
<button
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
type="button"
|
|
class={ utils.TwMerge(
|
|
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-left",
|
|
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
"transition-colors",
|
|
"data-[tui-sidebar-active=true]:bg-sidebar-accent data-[tui-sidebar-active=true]:text-sidebar-accent-foreground data-[tui-sidebar-active=true]:font-medium",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="menu-sub-button"
|
|
if p.IsActive {
|
|
data-tui-sidebar-active="true"
|
|
}
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</button>
|
|
}
|
|
}
|
|
|
|
templ Inset(props ...InsetProps) {
|
|
{{ var p InsetProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<main
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge(
|
|
"relative flex w-full flex-1 flex-col bg-background",
|
|
// Add special styling when peer sidebar has variant="inset"
|
|
"md:peer-data-[tui-sidebar-variant=inset]:m-2",
|
|
"md:peer-data-[tui-sidebar-variant=inset]:ml-0",
|
|
"md:peer-data-[tui-sidebar-variant=inset]:rounded-xl",
|
|
"md:peer-data-[tui-sidebar-variant=inset]:shadow-sm",
|
|
// When sidebar is collapsed (offcanvas mode) and variant is inset, add left margin back
|
|
"md:peer-data-[tui-sidebar-variant=inset]:peer-data-[tui-sidebar-state=collapsed]:peer-data-[tui-sidebar-collapsible=offcanvas]:ml-2",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="inset"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</main>
|
|
}
|
|
|
|
templ Group(props ...GroupProps) {
|
|
{{ var p GroupProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<div
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge("relative flex w-full min-w-0 flex-col p-2", p.Class) }
|
|
data-tui-sidebar="group"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ GroupLabel(props ...GroupLabelProps) {
|
|
{{ var p GroupLabelProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<div
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge(
|
|
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70",
|
|
"ring-sidebar-ring outline-none transition-[margin,opacity] duration-200 ease-linear",
|
|
"focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:-mt-8 group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:opacity-0",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="group-label"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</div>
|
|
}
|
|
|
|
templ MenuBadge(props ...MenuBadgeProps) {
|
|
{{ var p MenuBadgeProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<span
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge(
|
|
"ml-auto flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium",
|
|
"bg-sidebar-accent text-sidebar-accent-foreground",
|
|
"group-data-[tui-sidebar-state=collapsed]:group-data-[tui-sidebar-collapsible=icon]:hidden",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="menu-badge"
|
|
{ p.Attributes... }
|
|
>
|
|
{ children... }
|
|
</span>
|
|
}
|
|
|
|
templ Separator(props ...SeparatorProps) {
|
|
{{ var p SeparatorProps }}
|
|
if len(props) > 0 {
|
|
{{ p = props[0] }}
|
|
}
|
|
<hr
|
|
if p.ID != "" {
|
|
id={ p.ID }
|
|
}
|
|
class={ utils.TwMerge(
|
|
"mx-2 my-2 border-t border-sidebar-border",
|
|
p.Class,
|
|
) }
|
|
data-tui-sidebar="separator"
|
|
{ p.Attributes... }
|
|
/>
|
|
}
|
|
|
|
templ Script() {
|
|
<script defer nonce={ templ.GetNonce(ctx) } src={ utils.ScriptURL("/assets/js/sidebar.min.js") }></script>
|
|
}
|