cloudaxe-opencode/packages/tui/internal/components/modal/modal.go

146 lines
3.2 KiB
Go
Raw Normal View History

2025-06-12 14:35:19 +00:00
package modal
import (
2025-06-18 20:12:18 +00:00
"strings"
2025-06-12 14:35:19 +00:00
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode/internal/layout"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
)
2025-06-12 21:00:20 +00:00
// CloseModalMsg is a message to signal that the active modal should be closed.
type CloseModalMsg struct{}
2025-06-12 14:35:19 +00:00
// Modal is a reusable modal component that handles frame rendering and overlay placement
type Modal struct {
2025-06-12 21:00:20 +00:00
width int
height int
title string
maxWidth int
maxHeight int
fitContent bool
2025-06-12 14:35:19 +00:00
}
// ModalOption is a function that configures a Modal
type ModalOption func(*Modal)
// WithTitle sets the modal title
func WithTitle(title string) ModalOption {
return func(m *Modal) {
m.title = title
}
}
// WithMaxWidth sets the maximum width
func WithMaxWidth(width int) ModalOption {
return func(m *Modal) {
m.maxWidth = width
2025-06-12 21:00:20 +00:00
m.fitContent = false
2025-06-12 14:35:19 +00:00
}
}
// WithMaxHeight sets the maximum height
func WithMaxHeight(height int) ModalOption {
return func(m *Modal) {
m.maxHeight = height
}
}
2025-06-12 21:00:20 +00:00
func WithFitContent(fit bool) ModalOption {
2025-06-12 14:35:19 +00:00
return func(m *Modal) {
2025-06-12 21:00:20 +00:00
m.fitContent = fit
2025-06-12 14:35:19 +00:00
}
}
2025-06-12 21:00:20 +00:00
// New creates a new Modal with the given options
func New(opts ...ModalOption) *Modal {
2025-06-12 14:35:19 +00:00
m := &Modal{
2025-06-12 21:00:20 +00:00
maxWidth: 0,
maxHeight: 0,
fitContent: true,
2025-06-12 14:35:19 +00:00
}
for _, opt := range opts {
opt(m)
}
return m
}
2025-06-18 20:12:18 +00:00
func (m *Modal) SetTitle(title string) {
m.title = title
}
2025-06-12 21:00:20 +00:00
// Render renders the modal centered on the screen
func (m *Modal) Render(contentView string, background string) string {
2025-06-12 14:35:19 +00:00
t := theme.CurrentTheme()
outerWidth := layout.Current.Container.Width - 8
if m.maxWidth > 0 && outerWidth > m.maxWidth {
outerWidth = m.maxWidth
}
2025-06-12 21:00:20 +00:00
if m.fitContent {
titleWidth := lipgloss.Width(m.title)
contentWidth := lipgloss.Width(contentView)
largestWidth := max(titleWidth+2, contentWidth)
outerWidth = largestWidth + 6
}
2025-06-12 14:35:19 +00:00
innerWidth := outerWidth - 4
2025-06-12 21:00:20 +00:00
2025-07-04 15:29:40 +00:00
baseStyle := styles.NewStyle().Foreground(t.TextMuted()).Background(t.BackgroundPanel())
2025-06-12 14:35:19 +00:00
var finalContent string
if m.title != "" {
titleStyle := baseStyle.
2025-07-14 16:44:19 +00:00
Foreground(t.Text()).
2025-06-19 16:41:23 +00:00
Bold(true).
Padding(0, 1)
2025-06-12 21:00:20 +00:00
2025-06-22 11:09:23 +00:00
escStyle := baseStyle.Foreground(t.TextMuted())
2025-06-18 20:12:18 +00:00
escText := escStyle.Render("esc")
// Calculate position for esc text
titleWidth := lipgloss.Width(m.title)
escWidth := lipgloss.Width(escText)
2025-06-22 11:09:23 +00:00
spacesNeeded := max(0, innerWidth-titleWidth-escWidth-2)
2025-06-18 20:12:18 +00:00
spacer := strings.Repeat(" ", spacesNeeded)
titleLine := m.title + spacer + escText
titleLine = titleStyle.Render(titleLine)
2025-06-20 20:49:51 +00:00
finalContent = strings.Join([]string{titleLine, "", contentView}, "\n")
2025-06-12 14:35:19 +00:00
} else {
finalContent = contentView
}
modalStyle := baseStyle.
PaddingTop(1).
PaddingBottom(1).
PaddingLeft(2).
2025-06-19 16:41:23 +00:00
PaddingRight(2)
2025-06-12 21:00:20 +00:00
modalView := modalStyle.
2025-06-12 14:35:19 +00:00
Width(outerWidth).
Render(finalContent)
// Calculate position for centering
bgHeight := lipgloss.Height(background)
bgWidth := lipgloss.Width(background)
modalHeight := lipgloss.Height(modalView)
modalWidth := lipgloss.Width(modalView)
2025-06-12 21:00:20 +00:00
2025-06-12 14:35:19 +00:00
row := (bgHeight - modalHeight) / 2
col := (bgWidth - modalWidth) / 2
2025-06-12 21:00:20 +00:00
2025-06-12 14:35:19 +00:00
return layout.PlaceOverlay(
col-1, // TODO: whyyyyy
2025-06-12 14:35:19 +00:00
row,
modalView,
background,
2025-06-19 16:41:23 +00:00
layout.WithOverlayBorder(),
2025-07-14 16:44:19 +00:00
layout.WithOverlayBorderColor(t.BorderActive()),
2025-06-12 14:35:19 +00:00
)
}