Skip to content
Merged
10 changes: 5 additions & 5 deletions internal/iostreams/charm.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func charmInputPrompt(_ *IOStreams, _ context.Context, message string, cfg Input
if cfg.Required {
field.Validate(huh.ValidateMinLength(1))
}
err := huh.NewForm(huh.NewGroup(field)).Run()
err := huh.NewForm(huh.NewGroup(field)).WithTheme(ThemeSlack()).Run()
if err != nil {
return "", err
}
Expand All @@ -46,7 +46,7 @@ func charmConfirmPrompt(_ *IOStreams, _ context.Context, message string, default
field := huh.NewConfirm().
Title(message).
Value(&choice)
err := huh.NewForm(huh.NewGroup(field)).Run()
err := huh.NewForm(huh.NewGroup(field)).WithTheme(ThemeSlack()).Run()
if err != nil {
return false, err
}
Expand Down Expand Up @@ -76,7 +76,7 @@ func charmSelectPrompt(_ *IOStreams, _ context.Context, msg string, options []st
field.Height(cfg.PageSize + 2)
}

err := huh.NewForm(huh.NewGroup(field)).Run()
err := huh.NewForm(huh.NewGroup(field)).WithTheme(ThemeSlack()).Run()
if err != nil {
return SelectPromptResponse{}, err
}
Expand All @@ -95,7 +95,7 @@ func charmPasswordPrompt(_ *IOStreams, _ context.Context, message string, cfg Pa
if cfg.Required {
field.Validate(huh.ValidateMinLength(1))
}
err := huh.NewForm(huh.NewGroup(field)).Run()
err := huh.NewForm(huh.NewGroup(field)).WithTheme(ThemeSlack()).Run()
if err != nil {
return PasswordPromptResponse{}, err
}
Expand All @@ -115,7 +115,7 @@ func charmMultiSelectPrompt(_ *IOStreams, _ context.Context, message string, opt
Options(opts...).
Value(&selected)

err := huh.NewForm(huh.NewGroup(field)).Run()
err := huh.NewForm(huh.NewGroup(field)).WithTheme(ThemeSlack()).Run()
if err != nil {
return []string{}, err
}
Expand Down
120 changes: 120 additions & 0 deletions internal/iostreams/charm_theme.go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📠 question: Would it be better to move this to the internal/style package? I forget if import errors occur, but it might be nice to keep sequences separate from direct I/O operations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooo yes on it 🫡

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗣️ From direct message: This is going to be addressed with #355?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2022-2026 Salesforce, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package iostreams

// Slack brand theme for charmbracelet/huh prompts.
// Uses official Slack brand colors to give the CLI a fun, playful feel.

import (
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💄 praise: So great to see these styles shining!

)

// Slack brand colors according to https://a.slack-edge.com/4d5bb/marketing/img/media-kit/slack_brand_guidelines_september2020.pdf
var (
slackAubergine = lipgloss.Color("#7C2852")
slackBlue = lipgloss.Color("#36c5f0")
slackGreen = lipgloss.Color("#2eb67d")
slackYellow = lipgloss.Color("#ecb22e")
slackRed = lipgloss.Color("#e01e5a")
slackPool = lipgloss.Color("#78d7dd")
slackLegalGray = lipgloss.Color("#5e5d60")
slackOptionText = lipgloss.AdaptiveColor{Light: "#1d1c1d", Dark: "#f4ede4"}
slackDescriptionText = lipgloss.AdaptiveColor{Light: "#454447", Dark: "#b9b5b0"}
slackPlaceholderText = lipgloss.AdaptiveColor{Light: "#5e5d60", Dark: "#868380"}
)

// ThemeSlack returns a huh theme styled with Slack brand colors.
func ThemeSlack() *huh.Theme {
t := huh.ThemeBase()

// Focused styles apply to the field the user is currently interacting with.
// Blurred styles apply to visible fields that are not currently active.
t.Focused.Base = t.Focused.Base.
BorderForeground(slackAubergine)
t.Focused.Title = lipgloss.NewStyle().
Foreground(slackAubergine).
Bold(true)
t.Focused.Description = lipgloss.NewStyle().
Foreground(slackDescriptionText)
t.Focused.ErrorIndicator = lipgloss.NewStyle().
Foreground(slackRed).
SetString(" *")
t.Focused.ErrorMessage = lipgloss.NewStyle().
Foreground(slackRed)

// Select styles
t.Focused.SelectSelector = lipgloss.NewStyle().
Foreground(slackBlue).
SetString("❱ ")
t.Focused.Option = lipgloss.NewStyle().
Foreground(slackOptionText)
t.Focused.NextIndicator = lipgloss.NewStyle().
Foreground(slackPool).
MarginLeft(1).
SetString("↓")
t.Focused.PrevIndicator = lipgloss.NewStyle().
Foreground(slackPool).
MarginRight(1).
SetString("↑")

// Multi-select styles
t.Focused.MultiSelectSelector = lipgloss.NewStyle().
Foreground(slackYellow).
SetString("❱ ")
t.Focused.SelectedOption = lipgloss.NewStyle().
Foreground(slackGreen)
t.Focused.SelectedPrefix = lipgloss.NewStyle().
Foreground(slackGreen).
SetString("[✓] ")
t.Focused.UnselectedOption = lipgloss.NewStyle().
Foreground(slackOptionText)
t.Focused.UnselectedPrefix = lipgloss.NewStyle().
Foreground(slackLegalGray).
SetString("[ ] ")

// Text input styles
t.Focused.TextInput.Cursor = lipgloss.NewStyle().
Foreground(slackYellow)
t.Focused.TextInput.Prompt = lipgloss.NewStyle().
Foreground(slackBlue)
t.Focused.TextInput.Placeholder = lipgloss.NewStyle().
Foreground(slackPlaceholderText)
t.Focused.TextInput.Text = lipgloss.NewStyle().
Foreground(slackOptionText)

// Button styles
button := lipgloss.NewStyle().
Padding(0, 2).
MarginRight(1)
t.Focused.FocusedButton = button.
Foreground(lipgloss.Color("#ffffff")).
Background(slackAubergine).
Bold(true)
t.Focused.BlurredButton = button.
Foreground(slackLegalGray).
Background(lipgloss.Color("#f8f8f8"))

// Blurred field styles — subdued version of focused
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 question: I'm not familiar with "blurred" or "focused" styles... Can we expand this comment toward when these appear?

t.Blurred = t.Focused
t.Blurred.Base = t.Focused.Base.
BorderStyle(lipgloss.HiddenBorder())
t.Blurred.SelectSelector = lipgloss.NewStyle().SetString(" ")
t.Blurred.MultiSelectSelector = lipgloss.NewStyle().SetString(" ")
t.Blurred.NextIndicator = lipgloss.NewStyle()
t.Blurred.PrevIndicator = lipgloss.NewStyle()

return t
}
Loading