@@ -0,0 +1,123 @@
4+ "github.com/charmbracelet/bubbles/v2/key"
5+ tea "github.com/charmbracelet/bubbletea/v2"
6+ "github.com/charmbracelet/crush/internal/ui/common"
7+ "github.com/charmbracelet/lipgloss/v2"
10+// Model is a component that can be displayed on top of the UI.
11+type Model interface {
16+// Overlay manages multiple dialogs as an overlay.
22+// NewOverlay creates a new [Overlay] instance.
23+func NewOverlay(dialogs ...Model) *Overlay {
26+ keyMap: DefaultKeyMap(),
30+// ContainsDialog checks if a dialog with the specified ID exists.
31+func (d *Overlay) ContainsDialog(dialogID string) bool {
32+ for _, dialog := range d.dialogs {
33+ if dialog.ID() == dialogID {
40+// AddDialog adds a new dialog to the stack.
41+func (d *Overlay) AddDialog(dialog Model) {
42+ d.dialogs = append(d.dialogs, dialog)
45+// BringToFront brings the dialog with the specified ID to the front.
46+func (d *Overlay) BringToFront(dialogID string) {
47+ for i, dialog := range d.dialogs {
48+ if dialog.ID() == dialogID {
49+ // Move the dialog to the end of the slice
50+ d.dialogs = append(d.dialogs[:i], d.dialogs[i+1:]...)
51+ d.dialogs = append(d.dialogs, dialog)
57+// Update handles dialog updates.
58+func (d *Overlay) Update(msg tea.Msg) (*Overlay, tea.Cmd) {
59+ if len(d.dialogs) == 0 {
63+ idx := len(d.dialogs) - 1 // active dialog is the last one
64+ dialog := d.dialogs[idx]
65+ switch msg := msg.(type) {
66+ case tea.KeyPressMsg:
67+ if key.Matches(msg, d.keyMap.Close) {
68+ // Close the current dialog
74+ updatedDialog, cmd := dialog.Update(msg)
75+ if updatedDialog == nil {
76+ // Dialog requested to be closed
81+ // Update the dialog in the stack
82+ d.dialogs[idx] = updatedDialog
87+// View implements [Model].
88+func (d *Overlay) View() string {
89+ if len(d.dialogs) == 0 {
93+ // Compose all the dialogs into a single view
94+ canvas := lipgloss.NewCanvas()
95+ for _, dialog := range d.dialogs {
96+ layer := lipgloss.NewLayer(dialog.View())
97+ canvas.AddLayers(layer)
100+ return canvas.Render()
103+// ShortHelp implements [help.KeyMap].
104+func (d *Overlay) ShortHelp() []key.Binding {
105+ return []key.Binding{
110+// FullHelp implements [help.KeyMap].
111+func (d *Overlay) FullHelp() [][]key.Binding {
112+ return [][]key.Binding{
117+// removeDialog removes a dialog from the stack.
118+func (d *Overlay) removeDialog(idx int) {
119+ if idx < 0 || idx >= len(d.dialogs) {
122+ d.dialogs = append(d.dialogs[:idx], d.dialogs[idx+1:]...)