Files
LazyPM/pkg/tui/modal/base.go
Robin Olsen 7facfb6afe Merge pull request #73 from LazyBachelor/LPM-138
LPM-138 Refactor Tui with composable modals and use canvas

Refactors the TUI (dashboard + kanban) to use a shared, composable modal system with canvas-based overlay rendering, while consolidating styling into internal/style and expanding issue interactions (e.g., comments).

Changes:

Introduces a new pkg/tui/modal system (manager/stack + multiple modal types) and overlays modals using Lipgloss compositor layers.
Refactors dashboard/kanban views and input handling to use the modal manager + focus manager instead of per-modal boolean state.
Consolidates TUI styling by moving from pkg/tui/styles to internal/style and updates components to use the new style package; adds a shared footer renderer.
2026-03-20 16:28:10 +01:00

103 lines
2.0 KiB
Go

package modal
import (
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/LazyBachelor/LazyPM/internal/style"
)
// BaseModal provides common functionality for all modals.
// Embed this struct to get default implementations of the Modal interface.
type BaseModal struct {
id string
modType ModalType
active bool
width int
height int
}
// NewBaseModal creates a new base modal with the given properties
func NewBaseModal(id string, modType ModalType) BaseModal {
return BaseModal{
id: id,
modType: modType,
width: 80,
height: 20,
}
}
// ID returns the modal's unique identifier
func (b *BaseModal) ID() string {
return b.id
}
// Type returns the modal type
func (b *BaseModal) Type() ModalType {
return b.modType
}
// IsActive returns true if the modal is currently active
func (b *BaseModal) IsActive() bool {
return b.active
}
// SetSize updates the modal dimensions
func (b *BaseModal) SetSize(width, height int) {
if width < 1 {
width = 1
}
if height < 1 {
height = 1
}
b.width = width
b.height = height
}
// activate marks the modal as active
func (b *BaseModal) activate() tea.Cmd {
b.active = true
return nil
}
// deactivate marks the modal as inactive
func (b *BaseModal) deactivate() {
b.active = false
}
// Width returns the modal width
func (b *BaseModal) Width() int {
return b.width
}
// Height returns the modal height
func (b *BaseModal) Height() int {
return b.height
}
// ModalFrame renders content within a standard modal frame without full-screen placement
func ModalFrame(content string, width int) string {
if width < 5 {
return ""
}
boxWidth := max(min(80, width-4), 1)
return style.ModalContainerStyle.
Width(boxWidth).
Render(content)
}
// ModalWithLabel renders a modal with a label and content
func ModalWithLabel(label, content string, width int) string {
if width < 5 {
return ""
}
fullContent := lipgloss.JoinVertical(lipgloss.Left,
style.LabelStyle.Render(label),
content,
)
return ModalFrame(fullContent, width)
}