Skip to content

Latest commit

 

History

History
503 lines (399 loc) · 13.3 KB

File metadata and controls

503 lines (399 loc) · 13.3 KB

Layout

Layout primitives help you arrange UI elements in structured ways: horizontally, vertically, in boxes, or in tables.

1 Horizontal Layout - vui-hstack

Arranges children horizontally with optional spacing:

(vui-hstack
 (vui-text "One")
 (vui-text "Two")
 (vui-text "Three"))

Output:

One Two Three

1.1 Spacing

Default spacing is 1 character. Customize with :spacing:

;; No spacing
(vui-hstack :spacing 0
            (vui-text "A")
            (vui-text "B")
            (vui-text "C"))
;; Output: ABC

;; Extra spacing
(vui-hstack :spacing 3
            (vui-text "One")
            (vui-text "Two"))
;; Output: One   Two

1.2 Practical Example: Button Bar

(vui-hstack :spacing 2
            (vui-button "Save" :on-click #'save-data)
            (vui-button "Cancel" :on-click #'cancel)
            (vui-button "Delete" :on-click #'delete-item
                        :face '(:foreground "red")))

Output:

[Save]  [Cancel]  [Delete]

2 Vertical Layout - vui-vstack

Arranges children vertically with optional spacing and indentation:

(vui-vstack
 (vui-text "Line 1")
 (vui-text "Line 2")
 (vui-text "Line 3"))

Output:

Line 1
Line 2
Line 3

2.1 Spacing (Blank Lines)

Add blank lines between items with :spacing:

(vui-vstack :spacing 1
            (vui-text "Paragraph 1")
            (vui-text "Paragraph 2")
            (vui-text "Paragraph 3"))

Output:

Paragraph 1

Paragraph 2

Paragraph 3

2.2 Indentation

Indent all children with :indent:

(vui-vstack :indent 4
            (vui-text "- Item 1")
            (vui-text "- Item 2")
            (vui-text "- Item 3"))

Output:

- Item 1
- Item 2
- Item 3

2.3 Combining Spacing and Indent

(vui-fragment
 (vui-text "Tasks:")
 (vui-newline)
 (vui-vstack :spacing 1 :indent 2
             (vui-text "• Write documentation")
             (vui-text "• Add tests")
             (vui-text "• Deploy")))

Output:

Tasks:
  • Write documentation

  • Add tests

  • Deploy

3 Fixed-Width Box - vui-box

Creates a fixed-width container with text alignment:

(vui-box (vui-text "Centered")
         :width 20
         :align :center)

Output (20 chars wide):

Centered

3.1 Alignment Options

ValueDescription
:leftAlign to left (default)
:centerCenter in box
:rightAlign to right
(vui-vstack
 (vui-box (vui-text "Left") :width 20 :align :left)
 (vui-box (vui-text "Center") :width 20 :align :center)
 (vui-box (vui-text "Right") :width 20 :align :right))

Output:

Left
        Center
               Right

3.2 Padding

Add padding inside the box:

(vui-box (vui-text "Padded")
         :width 20
         :align :center
         :padding-left 2
         :padding-right 2)

The padding reduces the available content width.

3.3 Practical Example: Aligned Form

(vui-vstack
 (vui-hstack
  (vui-box (vui-text "Name:") :width 10 :align :right)
  (vui-field :value name :size 20))
 (vui-hstack
  (vui-box (vui-text "Email:") :width 10 :align :right)
  (vui-field :value email :size 20))
 (vui-hstack
  (vui-box (vui-text "Phone:") :width 10 :align :right)
  (vui-field :value phone :size 20)))

Output:

 Name: [                    ]
Email: [                    ]
Phone: [                    ]

4 Tables - vui-table

Creates formatted tables with columns, rows, and optional borders.

4.1 Basic Table

(vui-table
 :columns '((:min-width 8) (:min-width 10) (:min-width 8))
 :rows '(("Alice" "Developer" "NYC")
         ("Bob" "Designer" "LA")
         ("Carol" "Manager" "Chicago")))

Output:

Alice    Developer   NYC
Bob      Designer    LA
Carol    Manager     Chicago

4.2 With Headers and Borders

(vui-table
 :columns '((:header "Name" :width 8)
            (:header "Role" :width 10)
            (:header "Location" :width 10))
 :rows '(("Alice" "Developer" "NYC")
         ("Bob" "Designer" "LA"))
 :border :ascii)

Output:

+----------+------------+------------+
| Name     | Role       | Location   |
+----------+------------+------------+
| Alice    | Developer  | NYC        |
| Bob      | Designer   | LA         |
+----------+------------+------------+

Bordered tables automatically add padding around cell content (| value | instead of |value|) for readability.

4.3 Unicode Borders

(vui-table
 :columns '((:header "Name" :width 8)
            (:header "Role" :width 10))
 :rows '(("Alice" "Developer")
         ("Bob" "Designer"))
 :border :unicode)

Output:

┌──────────┬────────────┐
│ Name     │ Role       │
├──────────┼────────────┤
│ Alice    │ Developer  │
│ Bob      │ Designer   │
└──────────┴────────────┘

4.4 Column Alignment

(vui-table
 :columns '((:header "ID" :width 5 :align :right)
            (:header "Product" :width 12 :align :left)
            (:header "Price" :width 8 :align :right))
 :rows '(("1" "Widget" "$9.99")
         ("2" "Gadget" "$19.99")
         ("3" "Gizmo" "$29.99"))
 :border :ascii)

Output:

+-------+--------------+----------+
|    ID | Product      |    Price |
+-------+--------------+----------+
|     1 | Widget       |    $9.99 |
|     2 | Gadget       |   $19.99 |
|     3 | Gizmo        |   $29.99 |
+-------+--------------+----------+

4.5 Column Properties

PropertyDescription
:headerColumn header text
:widthTarget width for cell content
:min-widthMinimum width, expands as needed
:growIf t, pad short content and expand for long
:truncateIf t, truncate long content with “…”
:align:left, :center, or :right

4.5.1 Width Behavior

The :width, :grow, and :truncate options interact to control how columns handle content that doesn’t match the declared width:

:width:grow:truncateContent vs WidthResult
Wnilnilcontent < WColumn shrinks to content size
Wnilnilcontent > WOverflow with broken bar (¦)
Wtnilcontent < WColumn = W, content padded
Wtnilcontent > WColumn expands to fit content
Wniltcontent < WColumn shrinks to content size
Wniltcontent > WColumn = W, content truncated with “…”
Wttcontent < WColumn = W, content padded
Wttcontent > WColumn = W, content truncated with “…”
nil--anyColumn auto-sizes to content

Notes:

  • :grow t acts as a minimum width guarantee
  • :truncate t acts as a maximum width guarantee
  • Without :grow, short content shrinks the column
  • Without :truncate, long content either overflows (¦) or expands the column

4.6 Interactive Cells

Table cells can contain any vnode, including buttons, fields, and components:

(vui-table
 :columns '((:header "Item" :width 12)
            (:header "Qty" :width 6)
            (:header "Action" :width 10))
 :rows `(("Apples" "5" ,(vui-button "[Edit]" :on-click edit-apples))
         ("Oranges" "3" ,(vui-button "[Edit]" :on-click edit-oranges)))
 :border :ascii)

For interactive tables with complex state, embed components in cells:

(vui-table
 :columns '((:header "Name" :width 15)
            (:header "Score" :width 8))
 :rows (mapcar (lambda (item)
                 (list (plist-get item :name)
                       (vui-component 'editable-score
                                      :key (plist-get item :id)
                                      :value (plist-get item :score)
                                      :on-change handle-change)))
               items)
 :border :ascii)

4.7 Dynamic Column Widths

Calculate column widths dynamically based on content:

(let* ((max-name-len (apply #'max (mapcar #'length names)))
       (col-width (max 18 (min 48 (+ max-name-len 2)))))  ; min 18, max 48
  (vui-table
   :columns `((:header "Name" :width ,col-width)
              (:header "Value" :width 10))
   :rows ...))

See docs/examples/05-wine-tasting.el for a complete example with:

  • Interactive buttons in table cells
  • Dynamic column width calculation
  • Real-time statistics updates

5 Dynamic Lists - vui-list

Renders a list of items using a render function. Returns a vstack for vertical lists (default) or hstack for horizontal lists, ensuring proper indent propagation.

(vui-list '("Apple" "Banana" "Cherry")
          (lambda (item)
            (vui-text (format "%s" item))))

Output:

• Apple
• Banana
• Cherry

5.1 With Key Function

For efficient updates, provide a key function:

(vui-list todos
          (lambda (todo)
            (vui-component 'todo-item :todo todo))
          (lambda (todo)
            (plist-get todo :id)))  ; Key function

The key function returns a unique identifier for each item.

5.2 With Indentation

Lists properly inherit indent from parent containers, or you can set it directly:

;; Inherits indent from parent vstack
(vui-vstack :indent 2
            (vui-text "Items:")
            (vui-list items #'vui-text))

;; Or set indent directly
(vui-list items #'vui-text nil :indent 2)

5.3 Complex List Items

(vui-defcomponent product-list ()
  :state ((products '((:id 1 :name "Widget" :price 9.99)
                      (:id 2 :name "Gadget" :price 19.99))))
  :render
  (vui-vstack :spacing 1
              (vui-list products
                        (lambda (product)
                          (vui-hstack
                           (vui-text (plist-get product :name))
                           (vui-text (format " - $%.2f"
                                             (plist-get product :price)))))
                        (lambda (product)
                          (plist-get product :id)))))

6 Nesting Layouts

Layouts can be nested for complex structures:

(vui-vstack :spacing 1
            ;; Header row
            (vui-hstack :spacing 2
                        (vui-text "Dashboard" :face 'bold)
                        (vui-button "Refresh"))

            ;; Two-column content
            (vui-hstack :spacing 4
                        ;; Left column
                        (vui-vstack
                         (vui-text "Statistics")
                         (vui-text "Users: 100")
                         (vui-text "Sales: 50"))

                        ;; Right column
                        (vui-vstack
                         (vui-text "Actions")
                         (vui-button "Add User")
                         (vui-button "View Report"))))

7 Summary

PrimitivePurposeKey Props
vui-hstackHorizontal arrangement:spacing
vui-vstackVertical arrangement:spacing, :indent
vui-boxFixed-width container:width, :align, :padding-left/right
vui-tableTabular data:columns, :rows, :border
vui-listDynamic list renderingitems, render-fn, key-fn, :indent, :spacing

8 Try It Yourself

Exercise: Create a dashboard component that displays:

  1. A title “My Dashboard” at the top
  2. A table of tasks with columns: Status, Task, Due Date
  3. A button bar at the bottom with “Add Task” and “Clear Done” buttons
(vui-defcomponent dashboard ()
  :state ((tasks '(("[ ]" "Write docs" "2024-01-15")
                   ("[X]" "Add tests" "2024-01-10")
                   ("[ ]" "Deploy" "2024-01-20"))))
  :render
  ;; Your implementation here
  )

9 What’s Next?