summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/internal/components/modal
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tui/internal/components/modal')
-rw-r--r--packages/tui/internal/components/modal/modal.go137
1 files changed, 41 insertions, 96 deletions
diff --git a/packages/tui/internal/components/modal/modal.go b/packages/tui/internal/components/modal/modal.go
index 9477aee29..88e37c50b 100644
--- a/packages/tui/internal/components/modal/modal.go
+++ b/packages/tui/internal/components/modal/modal.go
@@ -1,25 +1,23 @@
package modal
import (
- "github.com/charmbracelet/bubbles/v2/key"
- tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode/internal/layout"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
)
+// CloseModalMsg is a message to signal that the active modal should be closed.
+type CloseModalMsg struct{}
+
// Modal is a reusable modal component that handles frame rendering and overlay placement
type Modal struct {
- content tea.Model
- width int
- height int
- title string
- showBorder bool
- borderStyle lipgloss.Border
- maxWidth int
- maxHeight int
- centerContent bool
+ width int
+ height int
+ title string
+ maxWidth int
+ maxHeight int
+ fitContent bool
}
// ModalOption is a function that configures a Modal
@@ -32,24 +30,11 @@ func WithTitle(title string) ModalOption {
}
}
-// WithBorder enables/disables the border
-func WithBorder(show bool) ModalOption {
- return func(m *Modal) {
- m.showBorder = show
- }
-}
-
-// WithBorderStyle sets the border style
-func WithBorderStyle(style lipgloss.Border) ModalOption {
- return func(m *Modal) {
- m.borderStyle = style
- }
-}
-
// WithMaxWidth sets the maximum width
func WithMaxWidth(width int) ModalOption {
return func(m *Modal) {
m.maxWidth = width
+ m.fitContent = false
}
}
@@ -60,22 +45,18 @@ func WithMaxHeight(height int) ModalOption {
}
}
-// WithCenterContent centers the content within the modal
-func WithCenterContent(center bool) ModalOption {
+func WithFitContent(fit bool) ModalOption {
return func(m *Modal) {
- m.centerContent = center
+ m.fitContent = fit
}
}
-// New creates a new Modal with the given content and options
-func New(content tea.Model, opts ...ModalOption) *Modal {
+// New creates a new Modal with the given options
+func New(opts ...ModalOption) *Modal {
m := &Modal{
- content: content,
- showBorder: true,
- borderStyle: lipgloss.ThickBorder(),
- maxWidth: 0,
- maxHeight: 0,
- centerContent: false,
+ maxWidth: 0,
+ maxHeight: 0,
+ fitContent: true,
}
for _, opt := range opts {
@@ -85,40 +66,24 @@ func New(content tea.Model, opts ...ModalOption) *Modal {
return m
}
-func (m *Modal) Init() tea.Cmd {
- return m.content.Init()
-}
-
-func (m *Modal) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- switch msg := msg.(type) {
- case tea.WindowSizeMsg:
- m.width = msg.Width
- m.height = msg.Height
- }
-
- // Pass all messages to the content
- var cmd tea.Cmd
- m.content, cmd = m.content.Update(msg)
- return m, cmd
-}
-
-func (m *Modal) View() string {
+// Render renders the modal centered on the screen
+func (m *Modal) Render(contentView string, background string) string {
t := theme.CurrentTheme()
-
- // Get the content view
- contentView := ""
- if v, ok := m.content.(layout.ModelWithView); ok {
- contentView = v.View()
- }
- // Calculate dimensions
outerWidth := layout.Current.Container.Width - 8
if m.maxWidth > 0 && outerWidth > m.maxWidth {
outerWidth = m.maxWidth
}
-
+
+ if m.fitContent {
+ titleWidth := lipgloss.Width(m.title)
+ contentWidth := lipgloss.Width(contentView)
+ largestWidth := max(titleWidth+2, contentWidth)
+ outerWidth = largestWidth + 6
+ }
+
innerWidth := outerWidth - 4
-
+
// Base style for the modal
baseStyle := styles.BaseStyle().
Background(t.BackgroundElement()).
@@ -132,7 +97,7 @@ func (m *Modal) View() string {
Bold(true).
Width(innerWidth).
Padding(0, 1)
-
+
titleView := titleStyle.Render(m.title)
finalContent = lipgloss.JoinVertical(
lipgloss.Left,
@@ -143,56 +108,36 @@ func (m *Modal) View() string {
finalContent = contentView
}
- // Apply modal styling
modalStyle := baseStyle.
PaddingTop(1).
PaddingBottom(1).
PaddingLeft(2).
- PaddingRight(2)
-
- if m.showBorder {
- modalStyle = modalStyle.
- BorderStyle(m.borderStyle).
- BorderLeft(true).
- BorderRight(true).
- BorderLeftForeground(t.BackgroundSubtle()).
- BorderLeftBackground(t.Background()).
- BorderRightForeground(t.BackgroundSubtle()).
- BorderRightBackground(t.Background())
- }
-
- return modalStyle.
+ PaddingRight(2).
+ BorderStyle(lipgloss.ThickBorder()).
+ BorderLeft(true).
+ BorderRight(true).
+ BorderLeftForeground(t.BackgroundSubtle()).
+ BorderLeftBackground(t.Background()).
+ BorderRightForeground(t.BackgroundSubtle()).
+ BorderRightBackground(t.Background())
+
+ modalView := modalStyle.
Width(outerWidth).
Render(finalContent)
-}
-// Render renders the modal centered on the screen
-func (m *Modal) Render(background string) string {
- modalView := m.View()
-
// Calculate position for centering
bgHeight := lipgloss.Height(background)
bgWidth := lipgloss.Width(background)
modalHeight := lipgloss.Height(modalView)
modalWidth := lipgloss.Width(modalView)
-
+
row := (bgHeight - modalHeight) / 2
col := (bgWidth - modalWidth) / 2
-
- // Use PlaceOverlay to render the modal on top of the background
+
return layout.PlaceOverlay(
col,
row,
modalView,
background,
- true, // shadow
)
}
-
-// BindingKeys returns the key bindings from the content if it implements layout.Bindings
-func (m *Modal) BindingKeys() []key.Binding {
- if b, ok := m.content.(layout.Bindings); ok {
- return b.BindingKeys()
- }
- return []key.Binding{}
-} \ No newline at end of file