Skip to content

Commit

Permalink
docs: dynamic readme
Browse files Browse the repository at this point in the history
  • Loading branch information
maaslalani committed May 10, 2024
1 parent b2166ee commit 816f96c
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 34 deletions.
85 changes: 80 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Huh?

<p>
<img alt="Hey there! I'm Glenn!" title="Hey there! I'm Glenn!" src="https://stuff.charm.sh/huh/glenn.png" width="400" />
<img alt="Hey there! Im Glenn!" title="Hey there! Im Glenn!" src="https://stuff.charm.sh/huh/glenn.png" width="400" />
<br><br>
<a href="https://github.com/charmbracelet/huh/releases"><img src="https://img.shields.io/github/release/charmbracelet/huh.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/charmbracelet/huh?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="Go Docs"></a>
Expand Down Expand Up @@ -86,7 +86,7 @@ form := huh.NewForm(
// Gather some final details about the order.
huh.NewGroup(
huh.NewInput().
Title("What's your name?").
Title("Whats your name?").
Value(&name).
// Validating fields is easy. The form will mark erroneous fields
// and display error messages accordingly.
Expand Down Expand Up @@ -125,6 +125,9 @@ if !discount {
And that’s it! For more info see [the full source][burgersource] for this
example as well as [the docs][docs].

If you need more dynamic forms that change based on input from previous fields,
check out the [dynamic forms](#dynamic) example.

[burgersource]: ./examples/burger/main.go
[docs]: https://pkg.go.dev/github.com/charmbracelet/huh?tab=doc

Expand All @@ -144,7 +147,7 @@ example as well as [the docs][docs].
var name string

huh.NewInput().
Title("What's your name?").
Title("Whats your name?").
Value(&name).
Run() // this is blocking...

Expand All @@ -159,7 +162,7 @@ Prompt the user for a single line of text.

```go
huh.NewInput().
Title("What's for lunch?").
Title("Whats for lunch?").
Prompt("?").
Validate(isFood).
Value(&lunch)
Expand Down Expand Up @@ -277,6 +280,78 @@ Themes can take advantage of the full range of

[lipgloss]: https://github.com/charmbracelet/lipgloss

## Dynamic

`huh?` forms can be as dynamic as your heart desires. Simply replace properties
with their equivalent `Func` to recompute the properties value every time a
different part of your form changes.

Here’s how you would build a simple country + state / province picker.

First, define some variables that we’ll use to store the user selection.

```go
var country string
var state string
```

Define your country select as you normally would:

```go
huh.NewSelect[string]().
Options(huh.NewOptions("United States", "Canada", "Mexico")...).
Value(&country).
Title("Country").
```

Define your state select with `TitleFunc` and `OptionsFunc` instead of `Title`
and `Options`. This will allow you to change the title and options based on the
selection of the previous field, i.e. `country`.

To do this, we provide a `func() string` and a `binding any` to `TitleFunc`. The
function defines what to show for the title and the binding specifies what value
needs to change for the function to recompute. So if `country` changes (e.g. the
user changes the selection) we will recompute the function.

For `OptionsFunc`, we provide a `func() []Option[string]` and a `binding any`.
We’ll fetch the country’s states, provinces, or territories from an API. `huh`
will automatically handle caching for you.

> [!IMPORTANT]
> We have to pass `&country` as the binding to recompute the function only when
> `country` changes, otherwise we will hit the API too often.
```go
huh.NewSelect[string]().
Value(&state).
Height(8).
TitleFunc(func() string {
switch country {
case "United States":
return "State"
case "Canada":
return "Province"
default:
return "Territory"
}
}, &country).
OptionsFunc(func() []huh.Option[string] {
opts := fetchStatesForCountry(country)
return huh.NewOptions(opts...)
}, &country),
```

Lastly, run the `form` with these inputs.

```go
err := form.Run()
if err != nil {
log.Fatal(err)
}
```

<img width="600" src="https://vhs.charm.sh/vhs-6FRmBjNi2aiRb4INPXwIjo.gif" alt="Country / State form with dynamic inputs running.">

## Bonus: Spinner

`huh?` ships with a standalone spinner package. It’s useful for indicating
Expand Down Expand Up @@ -404,7 +479,7 @@ For some `Huh?` programs in production, see:

## Feedback

We'd love to hear your thoughts on this project. Feel free to drop us a note!
Wed love to hear your thoughts on this project. Feel free to drop us a note!

- [Twitter](https://twitter.com/charmcli)
- [The Fediverse](https://mastodon.social/@charmcli)
Expand Down
35 changes: 35 additions & 0 deletions examples/dynamic/demo.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Output dynamic.gif

Set Shell "bash"
Set FontSize 28
Set Width 1000
Set Height 700

Hide
Type "clear && go build -o dynamic ./dynamic-country"
Enter
Sleep 1s
Show

Sleep 1s
Type "./dynamic" Sleep 500ms Enter

Sleep 3.5s
Down
Sleep 2.5s
Down
Sleep 2.5s
Enter
Sleep 1s
Down@150ms 12
Up@150ms 2

Sleep 1s

Enter

Sleep 3s

Hide
Type "rm dynamic"
Show
32 changes: 3 additions & 29 deletions examples/dynamic/dynamic-country/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"strings"
"time"

"github.com/charmbracelet/log"
Expand All @@ -15,7 +14,7 @@ func main() {

var (
country string
state []string
state string
)

form := huh.NewForm(
Expand All @@ -25,26 +24,8 @@ func main() {
Value(&country).
Title("Country").
Height(5),
huh.NewMultiSelect[string]().
Value(&state).
Height(8).
TitleFunc(func() string {
switch country {
case "United States":
return "State"
case "Canada":
return "Province"
default:
return "Territory"
}
}, &country).
OptionsFunc(func() []huh.Option[string] {
s := states[country]
// simulate API call
time.Sleep(1000 * time.Millisecond)
return huh.NewOptions(s...)
}, &country /* only this function when `country` changes */),
huh.NewSelect[string]().
Value(&state).
Height(8).
TitleFunc(func() string {
switch country {
Expand All @@ -62,13 +43,6 @@ func main() {
time.Sleep(1000 * time.Millisecond)
return huh.NewOptions(s...)
}, &country /* only this function when `country` changes */),
huh.NewNote().
TitleFunc(func() string {
return fmt.Sprintf("You selected: %s", country)
}, &country).
DescriptionFunc(func() string {
return fmt.Sprintf("You selected: %s", strings.Join(state, ", "))
}, []any{&country, &state}),
),
)

Expand All @@ -77,7 +51,7 @@ func main() {
log.Fatal(err)
}

fmt.Println(state)
fmt.Printf("%s, %s\n", state, country)
}

var states = map[string][]string{
Expand Down

0 comments on commit 816f96c

Please sign in to comment.