fix(ui): improve layout with global width constants and centered forms
- Add global MaxFormWidth (80) and MaxContentWidth (120) constants in styles - Center form containers on screen while keeping content left-aligned using lipgloss.Place - Apply theme colors to welcome logo (cyan) and version text (dimmed) - Update form builder to use MaxFormWidth constant instead of hardcoded value - Fix logo and form truncation issues by properly constraining container widths
- commit
- ba458a0fcb2f4e1a7eb7fec9b2e049d5aa2ed377
- author
- bnema · · 2025-10-28 03:32 UTC
- parent
- 2574ff7
- signature
- unsigned
4 files · +35 -10
M internal/ui/components/forms.go +3 -2
| @@ -79,8 +79,9 @@ func (fb *FormBuilder) Confirm(title, affirmative, negative string, value *bool) | |||
| 79 | 79 | // CreateForm creates a form with accessibility settings and custom theme | |
| 80 | 80 | func (fb *FormBuilder) CreateForm(groups ...*huh.Group) *huh.Form { | |
| 81 | 81 | formWidth := fb.width | |
| 82 | - | if formWidth > 76 { | |
| 83 | - | formWidth = 76 | |
| 82 | + | // Cap to MaxFormWidth from styles | |
| 83 | + | if formWidth > styles.MaxFormWidth { | |
| 84 | + | formWidth = styles.MaxFormWidth | |
| 84 | 85 | } | |
| 85 | 86 | if formWidth < 40 { | |
| 86 | 87 | formWidth = 40 | |
| @@ -79,8 +79,9 @@ func (fb *FormBuilder) Confirm(title, affirmative, negative string, value *bool) | |||||
| 79 | // CreateForm creates a form with accessibility settings and custom theme | 79 | // CreateForm creates a form with accessibility settings and custom theme | ||
| 80 | func (fb *FormBuilder) CreateForm(groups ...*huh.Group) *huh.Form { | 80 | func (fb *FormBuilder) CreateForm(groups ...*huh.Group) *huh.Form { | ||
| 81 | formWidth := fb.width | 81 | formWidth := fb.width | ||
| 82 | - | if formWidth > 76 { | 82 | + | // Cap to MaxFormWidth from styles |
| 83 | - | formWidth = 76 | 83 | + | if formWidth > styles.MaxFormWidth { |
| 84 | + | formWidth = styles.MaxFormWidth | |||
| 84 | } | 85 | } | ||
| 85 | if formWidth < 40 { | 86 | if formWidth < 40 { | ||
| 86 | formWidth = 40 | 87 | formWidth = 40 | ||
M internal/ui/styles/containers.go +14 -3
| @@ -2,17 +2,28 @@ package styles | |||
| 2 | 2 | ||
| 3 | 3 | import "github.com/charmbracelet/lipgloss" | |
| 4 | 4 | ||
| 5 | + | // Layout constants | |
| 6 | + | const ( | |
| 7 | + | MaxFormWidth = 80 // Maximum width for form containers | |
| 8 | + | MaxContentWidth = 120 // Maximum width for content (welcome, complete screens) | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // Alignment aliases for clarity | |
| 12 | + | const ( | |
| 13 | + | LeftAlign = lipgloss.Left | |
| 14 | + | CenterAlign = lipgloss.Center | |
| 15 | + | ) | |
| 16 | + | ||
| 5 | 17 | // Container styles for consistent layout | |
| 6 | 18 | var ( | |
| 7 | 19 | // CenteredContainerStyle - Used for welcome, complete, error screens | |
| 8 | 20 | CenteredContainerStyle = lipgloss.NewStyle(). | |
| 9 | - | MaxWidth(80). | |
| 21 | + | MaxWidth(MaxContentWidth). | |
| 10 | 22 | Padding(0, 2). | |
| 11 | 23 | AlignHorizontal(lipgloss.Center) | |
| 12 | 24 | ||
| 13 | - | // FormContainerStyle - Used for all forms | |
| 25 | + | // FormContainerStyle - Used for all forms (content left-aligned) | |
| 14 | 26 | FormContainerStyle = lipgloss.NewStyle(). | |
| 15 | - | MaxWidth(80). | |
| 16 | 27 | Padding(0, 2). | |
| 17 | 28 | AlignHorizontal(lipgloss.Left) | |
| 18 | 29 | ) | |
| @@ -2,17 +2,28 @@ package styles | |||||
| 2 | 2 | ||||
| 3 | import "github.com/charmbracelet/lipgloss" | 3 | import "github.com/charmbracelet/lipgloss" | ||
| 4 | 4 | ||||
| 5 | + | // Layout constants | |||
| 6 | + | const ( | |||
| 7 | + | MaxFormWidth = 80 // Maximum width for form containers | |||
| 8 | + | MaxContentWidth = 120 // Maximum width for content (welcome, complete screens) | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // Alignment aliases for clarity | |||
| 12 | + | const ( | |||
| 13 | + | LeftAlign = lipgloss.Left | |||
| 14 | + | CenterAlign = lipgloss.Center | |||
| 15 | + | ) | |||
| 16 | + | ||||
| 5 | // Container styles for consistent layout | 17 | // Container styles for consistent layout | ||
| 6 | var ( | 18 | var ( | ||
| 7 | // CenteredContainerStyle - Used for welcome, complete, error screens | 19 | // CenteredContainerStyle - Used for welcome, complete, error screens | ||
| 8 | CenteredContainerStyle = lipgloss.NewStyle(). | 20 | CenteredContainerStyle = lipgloss.NewStyle(). | ||
| 9 | - | MaxWidth(80). | 21 | + | MaxWidth(MaxContentWidth). |
| 10 | Padding(0, 2). | 22 | Padding(0, 2). | ||
| 11 | AlignHorizontal(lipgloss.Center) | 23 | AlignHorizontal(lipgloss.Center) | ||
| 12 | 24 | ||||
| 13 | - | // FormContainerStyle - Used for all forms | 25 | + | // FormContainerStyle - Used for all forms (content left-aligned) |
| 14 | FormContainerStyle = lipgloss.NewStyle(). | 26 | FormContainerStyle = lipgloss.NewStyle(). | ||
| 15 | - | MaxWidth(80). | |||
| 16 | Padding(0, 2). | 27 | Padding(0, 2). | ||
| 17 | AlignHorizontal(lipgloss.Left) | 28 | AlignHorizontal(lipgloss.Left) | ||
| 18 | ) | 29 | ) | ||
M internal/ui/views/forms.go +12 -2
| @@ -3,9 +3,10 @@ package views | |||
| 3 | 3 | import ( | |
| 4 | 4 | "github.com/bnema/archup/internal/ui/model" | |
| 5 | 5 | "github.com/bnema/archup/internal/ui/styles" | |
| 6 | + | "github.com/charmbracelet/lipgloss" | |
| 6 | 7 | ) | |
| 7 | 8 | ||
| 8 | - | // RenderForm renders any form view (left-aligned with padding) | |
| 9 | + | // RenderForm renders any form view (container centered, content left-aligned) | |
| 9 | 10 | func RenderForm(m model.UI) string { | |
| 10 | 11 | if m.CurrentForm() == nil { | |
| 11 | 12 | return "Loading form..." | |
| @@ -13,6 +14,15 @@ func RenderForm(m model.UI) string { | |||
| 13 | 14 | ||
| 14 | 15 | header := CenterText(m.RenderPhaseHeader(), m.Width()) | |
| 15 | 16 | form := m.CurrentForm().View() | |
| 17 | + | content := styles.FormContainerStyle.Render(header + "\n\n" + form) | |
| 16 | 18 | ||
| 17 | - | return styles.FormContainerStyle.Render(header + "\n\n" + form) | |
| 19 | + | // Center the container on screen, but keep form content left-aligned | |
| 20 | + | return lipgloss.Place( | |
| 21 | + | m.Width(), | |
| 22 | + | m.Height(), | |
| 23 | + | lipgloss.Center, | |
| 24 | + | lipgloss.Top, | |
| 25 | + | content, | |
| 26 | + | lipgloss.WithWhitespaceChars(" "), | |
| 27 | + | ) | |
| 18 | 28 | } | |
| @@ -3,9 +3,10 @@ package views | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "github.com/bnema/archup/internal/ui/model" | 4 | "github.com/bnema/archup/internal/ui/model" | ||
| 5 | "github.com/bnema/archup/internal/ui/styles" | 5 | "github.com/bnema/archup/internal/ui/styles" | ||
| 6 | + | "github.com/charmbracelet/lipgloss" | |||
| 6 | ) | 7 | ) | ||
| 7 | 8 | ||||
| 8 | - | // RenderForm renders any form view (left-aligned with padding) | 9 | + | // RenderForm renders any form view (container centered, content left-aligned) |
| 9 | func RenderForm(m model.UI) string { | 10 | func RenderForm(m model.UI) string { | ||
| 10 | if m.CurrentForm() == nil { | 11 | if m.CurrentForm() == nil { | ||
| 11 | return "Loading form..." | 12 | return "Loading form..." | ||
| @@ -13,6 +14,15 @@ func RenderForm(m model.UI) string { | |||||
| 13 | 14 | ||||
| 14 | header := CenterText(m.RenderPhaseHeader(), m.Width()) | 15 | header := CenterText(m.RenderPhaseHeader(), m.Width()) | ||
| 15 | form := m.CurrentForm().View() | 16 | form := m.CurrentForm().View() | ||
| 17 | + | content := styles.FormContainerStyle.Render(header + "\n\n" + form) | |||
| 16 | 18 | ||||
| 17 | - | return styles.FormContainerStyle.Render(header + "\n\n" + form) | 19 | + | // Center the container on screen, but keep form content left-aligned |
| 20 | + | return lipgloss.Place( | |||
| 21 | + | m.Width(), | |||
| 22 | + | m.Height(), | |||
| 23 | + | lipgloss.Center, | |||
| 24 | + | lipgloss.Top, | |||
| 25 | + | content, | |||
| 26 | + | lipgloss.WithWhitespaceChars(" "), | |||
| 27 | + | ) | |||
| 18 | } | 28 | } | ||
M internal/ui/views/helpers.go +6 -3
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "fmt" | |
| 5 | 5 | "strings" | |
| 6 | 6 | ||
| 7 | + | "github.com/bnema/archup/internal/ui/styles" | |
| 7 | 8 | "github.com/charmbracelet/lipgloss" | |
| 8 | 9 | ) | |
| 9 | 10 | ||
| @@ -51,7 +52,7 @@ func (l *Logo) RenderCentered(termWidth int) string { | |||
| 51 | 52 | return l.Render() | |
| 52 | 53 | } | |
| 53 | 54 | ||
| 54 | - | // Render renders the logo | |
| 55 | + | // Render renders the logo with theme colors | |
| 55 | 56 | func (l *Logo) Render() string { | |
| 56 | 57 | lines := strings.Split(logoASCII, "\n") | |
| 57 | 58 | var rendered strings.Builder | |
| @@ -62,12 +63,14 @@ func (l *Logo) Render() string { | |||
| 62 | 63 | continue | |
| 63 | 64 | } | |
| 64 | 65 | ||
| 65 | - | rendered.WriteString(line) | |
| 66 | + | // Apply bright cyan color to the logo | |
| 67 | + | rendered.WriteString(styles.LogoCyanStyle.Render(line)) | |
| 66 | 68 | rendered.WriteString("\n") | |
| 67 | 69 | } | |
| 68 | 70 | ||
| 69 | 71 | rendered.WriteString("\n") | |
| 70 | - | rendered.WriteString(fmt.Sprintf(" %s\n", l.version)) | |
| 72 | + | // Apply dimmed style to version | |
| 73 | + | rendered.WriteString(styles.HelpStyle.Render(fmt.Sprintf(" %s\n", l.version))) | |
| 71 | 74 | ||
| 72 | 75 | return rendered.String() | |
| 73 | 76 | } | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "fmt" | 4 | "fmt" | ||
| 5 | "strings" | 5 | "strings" | ||
| 6 | 6 | ||||
| 7 | + | "github.com/bnema/archup/internal/ui/styles" | |||
| 7 | "github.com/charmbracelet/lipgloss" | 8 | "github.com/charmbracelet/lipgloss" | ||
| 8 | ) | 9 | ) | ||
| 9 | 10 | ||||
| @@ -51,7 +52,7 @@ func (l *Logo) RenderCentered(termWidth int) string { | |||||
| 51 | return l.Render() | 52 | return l.Render() | ||
| 52 | } | 53 | } | ||
| 53 | 54 | ||||
| 54 | - | // Render renders the logo | 55 | + | // Render renders the logo with theme colors |
| 55 | func (l *Logo) Render() string { | 56 | func (l *Logo) Render() string { | ||
| 56 | lines := strings.Split(logoASCII, "\n") | 57 | lines := strings.Split(logoASCII, "\n") | ||
| 57 | var rendered strings.Builder | 58 | var rendered strings.Builder | ||
| @@ -62,12 +63,14 @@ func (l *Logo) Render() string { | |||||
| 62 | continue | 63 | continue | ||
| 63 | } | 64 | } | ||
| 64 | 65 | ||||
| 65 | - | rendered.WriteString(line) | 66 | + | // Apply bright cyan color to the logo |
| 67 | + | rendered.WriteString(styles.LogoCyanStyle.Render(line)) | |||
| 66 | rendered.WriteString("\n") | 68 | rendered.WriteString("\n") | ||
| 67 | } | 69 | } | ||
| 68 | 70 | ||||
| 69 | rendered.WriteString("\n") | 71 | rendered.WriteString("\n") | ||
| 70 | - | rendered.WriteString(fmt.Sprintf(" %s\n", l.version)) | 72 | + | // Apply dimmed style to version |
| 73 | + | rendered.WriteString(styles.HelpStyle.Render(fmt.Sprintf(" %s\n", l.version))) | |||
| 71 | 74 | ||||
| 72 | return rendered.String() | 75 | return rendered.String() | ||
| 73 | } | 76 | } | ||