Scrollable list component with keyboard navigation and item selection.
(require '[charm.components.list :as item-list])
(def my-list (item-list/item-list ["Apple" "Banana" "Cherry"]))
;; In update function
(let [[list cmd] (item-list/list-update my-list msg)]
;; Handle navigation
)
;; In view function
(item-list/list-view my-list)
;; > Apple
;; Banana
;; Cherry(item-list/item-list items & options)| Option | Type | Default | Description |
|---|---|---|---|
items |
vector | required | Items to display |
:height |
int | 0 |
Visible height (0 = show all) |
:width |
int | 0 |
Width constraint (0 = unlimited) |
:cursor |
int | 0 |
Initial selected index |
:title |
string | nil |
Optional list title |
:show-title |
boolean | true |
Show title if provided |
:cursor-prefix |
string | "> " |
Prefix for selected item |
:item-prefix |
string | " " |
Prefix for unselected items |
:show-descriptions |
boolean | false |
Show item descriptions |
:infinite-scroll |
boolean | false |
Wrap around at ends |
:cursor-style |
style | cyan+bold | Style for selected item |
:item-style |
style | nil |
Style for unselected items |
:title-style |
style | bold | Style for title |
:id |
any | random | Unique identifier |
Items can be strings or maps:
;; Simple strings
(item-list/item-list ["Apple" "Banana" "Cherry"])
;; Maps with title and description
(item-list/item-list [{:title "Apple" :description "A red fruit"}
{:title "Banana" :description "A yellow fruit"}]
:show-descriptions true)
;; Maps with custom keys
(item-list/item-list [{:name "Alice" :role "Admin"}
{:name "Bob" :role "User"}])
;; Uses :name as title, :role as description| Action | Keys |
|---|---|
| Move up | Up, k, Ctrl+P |
| Move down | Down, j, Ctrl+N |
| Page up | PgUp, Ctrl+U |
| Page down | PgDn, Ctrl+D |
| Go to start | Home, g |
| Go to end | End, G |
(item-list/list-update list msg) ; => [list cmd]Handle navigation messages.
(item-list/list-view list) ; => "> Apple\n Banana\n Cherry"Render the list as a string.
(item-list/selected-item list) ; => "Apple" or {:title "Apple" ...}Get the currently selected item.
(item-list/selected-index list) ; => 0Get the index of the selected item.
(item-list/set-items list new-items)Replace all items, adjusting cursor if needed.
(item-list/select list 2) ; Select item at index 2(item-list/cursor-up list)
(item-list/cursor-down list)
(item-list/page-up list)
(item-list/page-down list)
(item-list/go-to-start list)
(item-list/go-to-end list)(ns my-app
(:require
[charm.components.list :as item-list]
[charm.message :as msg]
[charm.program :as program]
[charm.style.core :as style]))
(def items
[{:title "New File" :description "Create a new file"}
{:title "Open File" :description "Open an existing file"}
{:title "Save" :description "Save current file"}
{:title "Exit" :description "Quit the application"}])
(defn init []
[{:menu (item-list/item-list items
:height 10
:show-descriptions true
:cursor-style (style/style :fg style/yellow :bold true))}
nil])
(defn update-fn [state msg]
(cond
(msg/key-match? msg "enter")
(let [selected (item-list/selected-item (:menu state))]
(println "Selected:" (:title selected))
(if (= "Exit" (:title selected))
[state program/quit-cmd]
[state nil]))
(msg/key-match? msg "q")
[state program/quit-cmd]
:else
(let [[menu cmd] (item-list/list-update (:menu state) msg)]
[(assoc state :menu menu) cmd])))
(defn view [state]
(str "Select an option:\n\n"
(item-list/list-view (:menu state))
"\n\nEnter to select, q to quit"))
(program/run {:init init :update update-fn :view view})When :height is set, the list scrolls to keep the cursor visible:
(item-list/item-list (range 100)
:height 10) ; Shows 10 items, scrolls with cursor;; Filter items by predicate
(item-list/filter-items list #(str/includes? (:title %) "search"))
;; Find and select first match
(item-list/select-first-match list #(= "target" (:title %)))