Includes getting started guide, hiccup views reference, full API documentation, and annotated example walkthroughs with ASCII output examples. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
543 lines
11 KiB
Markdown
543 lines
11 KiB
Markdown
# Hiccup Views
|
|
|
|
Clojure TUI uses Hiccup-style syntax for declarative UI definitions. Views are pure functions that return nested data structures representing the UI.
|
|
|
|
## Basic Syntax
|
|
|
|
Views use vectors with keywords as element tags:
|
|
|
|
```clojure
|
|
[:element-type {attributes} children...]
|
|
```
|
|
|
|
Examples:
|
|
```clojure
|
|
[:text "Hello"] ;; Simple text
|
|
[:text {:fg :red} "Error"] ;; Text with attributes
|
|
[:col [:text "Line 1"] [:text "Line 2"]] ;; Nested elements
|
|
```
|
|
|
|
## Elements
|
|
|
|
### `:text` - Styled Text
|
|
|
|
The basic building block for displaying text.
|
|
|
|
```clojure
|
|
;; Simple text
|
|
[:text "Hello, World!"]
|
|
|
|
;; Styled text
|
|
[:text {:fg :cyan :bold true} "Important"]
|
|
|
|
;; Multiple style attributes
|
|
[:text {:fg :white :bg :red :bold true :underline true} "Alert!"]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
Hello, World!
|
|
Important
|
|
Alert!
|
|
```
|
|
|
|
**Attributes:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `:fg` | keyword/int | Foreground color |
|
|
| `:bg` | keyword/int | Background color |
|
|
| `:bold` | boolean | Bold text |
|
|
| `:dim` | boolean | Dimmed text |
|
|
| `:italic` | boolean | Italic text |
|
|
| `:underline` | boolean | Underlined text |
|
|
| `:inverse` | boolean | Swap fg/bg colors |
|
|
| `:strike` | boolean | Strikethrough text |
|
|
|
|
### `:row` - Horizontal Layout
|
|
|
|
Arranges children horizontally (left to right).
|
|
|
|
```clojure
|
|
;; Basic row
|
|
[:row "Left" "Middle" "Right"]
|
|
|
|
;; Row with gap
|
|
[:row {:gap 2} "A" "B" "C"]
|
|
|
|
;; Nested elements in row
|
|
[:row
|
|
[:text {:fg :green} "Status:"]
|
|
[:text {:bold true} "OK"]]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
LeftMiddleRight
|
|
|
|
A B C
|
|
|
|
Status:OK
|
|
```
|
|
|
|
**Attributes:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `:gap` | integer | Spaces between children (default: 0) |
|
|
|
|
### `:col` - Vertical Layout
|
|
|
|
Arranges children vertically (top to bottom).
|
|
|
|
```clojure
|
|
;; Basic column
|
|
[:col "Line 1" "Line 2" "Line 3"]
|
|
|
|
;; Column with gap
|
|
[:col {:gap 1}
|
|
[:text "Section 1"]
|
|
[:text "Section 2"]]
|
|
|
|
;; Nested layouts
|
|
[:col
|
|
[:text {:bold true} "Header"]
|
|
[:row "Col A" "Col B" "Col C"]
|
|
[:text {:fg :gray} "Footer"]]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
Line 1
|
|
Line 2
|
|
Line 3
|
|
|
|
Section 1
|
|
|
|
Section 2
|
|
|
|
Header
|
|
Col ACol BCol C
|
|
Footer
|
|
```
|
|
|
|
**Attributes:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `:gap` | integer | Blank lines between children (default: 0) |
|
|
|
|
### `:box` - Bordered Container
|
|
|
|
Wraps content in a bordered box.
|
|
|
|
```clojure
|
|
;; Simple box
|
|
[:box "Content"]
|
|
|
|
;; Box with title
|
|
[:box {:title "Settings"} "Options go here"]
|
|
|
|
;; Box with padding
|
|
[:box {:padding 1} "Padded content"]
|
|
|
|
;; Box with custom border style
|
|
[:box {:border :double} "Important!"]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
╭─────────╮
|
|
│Content │
|
|
╰─────────╯
|
|
|
|
╭─Settings─╮
|
|
│Options go here│
|
|
╰──────────╯
|
|
|
|
╭──────────────────╮
|
|
│ │
|
|
│ Padded content │
|
|
│ │
|
|
╰──────────────────╯
|
|
|
|
╔═══════════╗
|
|
║Important! ║
|
|
╚═══════════╝
|
|
```
|
|
|
|
**Attributes:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `:border` | keyword | Border style (see below) |
|
|
| `:title` | string | Title in top border |
|
|
| `:padding` | int/vec | Inner padding (see below) |
|
|
| `:width` | integer | Fixed width |
|
|
|
|
**Border Styles:**
|
|
|
|
| Style | Characters | Example |
|
|
|-------|------------|---------|
|
|
| `:rounded` | `╭╮╰╯─│` | `╭───╮` (default) |
|
|
| `:single` | `┌┐└┘─│` | `┌───┐` |
|
|
| `:double` | `╔╗╚╝═║` | `╔═══╗` |
|
|
| `:heavy` | `┏┓┗┛━┃` | `┏━━━┓` |
|
|
| `:ascii` | `++--\|` | `+---+` |
|
|
|
|
**Padding:**
|
|
|
|
```clojure
|
|
;; All sides
|
|
[:box {:padding 2} "Content"]
|
|
|
|
;; Vertical and horizontal [v h]
|
|
[:box {:padding [1 2]} "Content"]
|
|
|
|
;; Individual [top right bottom left]
|
|
[:box {:padding [1 2 1 2]} "Content"]
|
|
```
|
|
|
|
### `:space` - Empty Space
|
|
|
|
Creates empty space for layout purposes.
|
|
|
|
```clojure
|
|
;; Default 1x1 space
|
|
[:space]
|
|
|
|
;; Horizontal space
|
|
[:row "Left" [:space {:width 10}] "Right"]
|
|
|
|
;; Vertical space
|
|
[:col "Top" [:space {:height 3}] "Bottom"]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
Left Right
|
|
|
|
Top
|
|
|
|
|
|
|
|
Bottom
|
|
```
|
|
|
|
**Attributes:**
|
|
|
|
| Attribute | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `:width` | integer | Width in characters (default: 1) |
|
|
| `:height` | integer | Height in lines (default: 1) |
|
|
|
|
## Colors
|
|
|
|
### Named Colors
|
|
|
|
Basic 16-color palette supported by all terminals:
|
|
|
|
| Color | Keyword | Bright Version |
|
|
|-------|---------|----------------|
|
|
| Black | `:black` | `:bright-black` |
|
|
| Red | `:red` | `:bright-red` |
|
|
| Green | `:green` | `:bright-green` |
|
|
| Yellow | `:yellow` | `:bright-yellow` |
|
|
| Blue | `:blue` | `:bright-blue` |
|
|
| Magenta | `:magenta` | `:bright-magenta` |
|
|
| Cyan | `:cyan` | `:bright-cyan` |
|
|
| White | `:white` | `:bright-white` |
|
|
| Default | `:default` | - |
|
|
|
|
Aliases: `:gray` and `:grey` map to `:bright-black`
|
|
|
|
```clojure
|
|
[:text {:fg :red} "Error"]
|
|
[:text {:fg :bright-green} "Success"]
|
|
[:text {:bg :blue :fg :white} "Highlighted"]
|
|
```
|
|
|
|
### 256 Colors
|
|
|
|
Use integers 0-255 for extended color support:
|
|
|
|
```clojure
|
|
[:text {:fg 208} "Orange (256-color)"]
|
|
[:text {:bg 236} "Dark gray background"]
|
|
```
|
|
|
|
**Color ranges:**
|
|
- 0-7: Standard colors
|
|
- 8-15: Bright colors
|
|
- 16-231: 6x6x6 color cube
|
|
- 232-255: Grayscale (dark to light)
|
|
|
|
### True Color (24-bit)
|
|
|
|
For true color, use the `tui.ansi` namespace directly:
|
|
|
|
```clojure
|
|
(require '[tui.ansi :as ansi])
|
|
|
|
[:text (ansi/fg-rgb 255 128 0 "Orange text")]
|
|
[:text (ansi/bg-rgb 30 30 30 "Dark background")]
|
|
```
|
|
|
|
## Text Styles
|
|
|
|
Combine multiple styles:
|
|
|
|
```clojure
|
|
[:text {:bold true :underline true} "Bold and underlined"]
|
|
[:text {:fg :red :bold true :inverse true} "Inverted error"]
|
|
[:text {:dim true :italic true} "Subtle italic"]
|
|
```
|
|
|
|
**Available styles:**
|
|
|
|
| Style | Attribute | Description |
|
|
|-------|-----------|-------------|
|
|
| Bold | `:bold true` | Heavier font weight |
|
|
| Dim | `:dim true` | Lighter/faded text |
|
|
| Italic | `:italic true` | Slanted text |
|
|
| Underline | `:underline true` | Line under text |
|
|
| Inverse | `:inverse true` | Swap foreground/background |
|
|
| Strikethrough | `:strike true` | Line through text |
|
|
|
|
## Layout Examples
|
|
|
|
### Two-Column Layout
|
|
|
|
```clojure
|
|
[:row {:gap 4}
|
|
[:col
|
|
[:text {:bold true} "Left Column"]
|
|
[:text "Item 1"]
|
|
[:text "Item 2"]]
|
|
[:col
|
|
[:text {:bold true} "Right Column"]
|
|
[:text "Item A"]
|
|
[:text "Item B"]]]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
Left Column Right Column
|
|
Item 1 Item A
|
|
Item 2 Item B
|
|
```
|
|
|
|
### Nested Boxes
|
|
|
|
```clojure
|
|
[:box {:title "Outer" :padding 1}
|
|
[:row {:gap 2}
|
|
[:box {:border :single :title "Box A"}
|
|
[:text "Content A"]]
|
|
[:box {:border :single :title "Box B"}
|
|
[:text "Content B"]]]]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
╭─Outer────────────────────────────╮
|
|
│ │
|
|
│ ┌─Box A─────┐ ┌─Box B─────┐ │
|
|
│ │Content A │ │Content B │ │
|
|
│ └───────────┘ └───────────┘ │
|
|
│ │
|
|
╰──────────────────────────────────╯
|
|
```
|
|
|
|
### Status Bar
|
|
|
|
```clojure
|
|
[:col
|
|
[:box {:border :rounded :padding [0 1]}
|
|
[:text {:bold true} "My Application"]]
|
|
[:space {:height 1}]
|
|
[:text "Main content here..."]
|
|
[:space {:height 1}]
|
|
[:row {:gap 2}
|
|
[:text {:fg :gray} "Status: Ready"]
|
|
[:text {:fg :gray} "|"]
|
|
[:text {:fg :gray} "Press q to quit"]]]
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
╭──────────────────╮
|
|
│ My Application │
|
|
╰──────────────────╯
|
|
|
|
Main content here...
|
|
|
|
Status: Ready | Press q to quit
|
|
```
|
|
|
|
### Menu with Selection
|
|
|
|
```clojure
|
|
(defn menu-item [label selected?]
|
|
[:row
|
|
[:text (if selected? "> " " ")]
|
|
[:text {:fg (if selected? :cyan :white)
|
|
:bold selected?}
|
|
label]])
|
|
|
|
(defn view [{:keys [items cursor]}]
|
|
[:col
|
|
[:text {:bold true} "Select an option:"]
|
|
[:space {:height 1}]
|
|
[:col {:gap 0}
|
|
(for [[idx item] (map-indexed vector items)]
|
|
(menu-item item (= idx cursor)))]])
|
|
```
|
|
|
|
**Output (cursor on second item):**
|
|
```
|
|
Select an option:
|
|
|
|
First Option
|
|
> Second Option
|
|
Third Option
|
|
```
|
|
|
|
### Progress Indicator
|
|
|
|
```clojure
|
|
(defn progress-bar [percent width]
|
|
(let [filled (int (* width (/ percent 100)))
|
|
empty (- width filled)]
|
|
[:row
|
|
[:text "["]
|
|
[:text {:fg :green} (apply str (repeat filled "="))]
|
|
[:text {:fg :gray} (apply str (repeat empty "-"))]
|
|
[:text "]"]
|
|
[:text " "]
|
|
[:text (str percent "%")]]))
|
|
|
|
(defn view [{:keys [progress]}]
|
|
[:col
|
|
[:text "Downloading..."]
|
|
(progress-bar progress 20)])
|
|
```
|
|
|
|
**Output (at 65%):**
|
|
```
|
|
Downloading...
|
|
[=============-------] 65%
|
|
```
|
|
|
|
## Helper Functions
|
|
|
|
The `tui.render` namespace provides helper functions:
|
|
|
|
```clojure
|
|
(require '[tui.render :refer [text row col box]])
|
|
|
|
;; These are equivalent:
|
|
[:text {:fg :red} "Error"]
|
|
(text {:fg :red} "Error")
|
|
|
|
[:row {:gap 2} "A" "B"]
|
|
(row {:gap 2} "A" "B")
|
|
|
|
[:col [:text "Line 1"] [:text "Line 2"]]
|
|
(col (text "Line 1") (text "Line 2"))
|
|
|
|
[:box {:title "Info"} "Content"]
|
|
(box {:title "Info"} "Content")
|
|
```
|
|
|
|
## Conditional Rendering
|
|
|
|
Use standard Clojure conditionals:
|
|
|
|
```clojure
|
|
(defn view [{:keys [loading? error data]}]
|
|
[:col
|
|
[:text {:bold true} "Status"]
|
|
(cond
|
|
loading?
|
|
[:text {:fg :yellow} "Loading..."]
|
|
|
|
error
|
|
[:text {:fg :red} (str "Error: " error)]
|
|
|
|
:else
|
|
[:text {:fg :green} (str "Data: " data)])])
|
|
```
|
|
|
|
## Dynamic Views with `for`
|
|
|
|
Generate repeated elements:
|
|
|
|
```clojure
|
|
(defn view [{:keys [items selected]}]
|
|
[:col
|
|
(for [[idx item] (map-indexed vector items)]
|
|
[:row
|
|
[:text (if (= idx selected) "> " " ")]
|
|
[:text {:fg (if (= idx selected) :cyan :white)} item]])])
|
|
```
|
|
|
|
## String Shortcuts
|
|
|
|
Plain strings are automatically wrapped in `:text`:
|
|
|
|
```clojure
|
|
;; These are equivalent:
|
|
[:col "Line 1" "Line 2"]
|
|
[:col [:text "Line 1"] [:text "Line 2"]]
|
|
|
|
;; In rows too:
|
|
[:row "A" "B" "C"]
|
|
[:row [:text "A"] [:text "B"] [:text "C"]]
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Styled Labels
|
|
|
|
```clojure
|
|
(defn label [text]
|
|
[:text {:fg :gray} (str text ": ")])
|
|
|
|
(defn value [text]
|
|
[:text {:fg :white :bold true} text])
|
|
|
|
[:row (label "Name") (value "John")]
|
|
```
|
|
|
|
### Conditional Styling
|
|
|
|
```clojure
|
|
(defn status-text [status]
|
|
[:text {:fg (case status
|
|
:ok :green
|
|
:warning :yellow
|
|
:error :red
|
|
:white)
|
|
:bold (= status :error)}
|
|
(name status)])
|
|
```
|
|
|
|
### Reusable Components
|
|
|
|
```clojure
|
|
(defn card [{:keys [title]} & children]
|
|
[:box {:border :rounded :title title :padding [0 1]}
|
|
(into [:col] children)])
|
|
|
|
;; Usage
|
|
(card {:title "User Info"}
|
|
[:row (label "Name") (value "Alice")]
|
|
[:row (label "Email") (value "alice@example.com")])
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
- [API Reference](api-reference.md) - Complete API documentation
|
|
- [Examples](examples.md) - Full example applications
|