bubbles 0.0.5 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/LICENSE.txt +21 -0
- data/README.md +601 -80
- data/bubbles.gemspec +29 -21
- data/lib/bubbles/ansi.rb +71 -0
- data/lib/bubbles/cryptic_spinner.rb +274 -0
- data/lib/bubbles/cursor.rb +169 -0
- data/lib/bubbles/file_picker.rb +397 -0
- data/lib/bubbles/help.rb +165 -0
- data/lib/bubbles/key.rb +96 -0
- data/lib/bubbles/list.rb +365 -0
- data/lib/bubbles/paginator.rb +158 -0
- data/lib/bubbles/progress.rb +276 -0
- data/lib/bubbles/spinner/spinners.rb +77 -0
- data/lib/bubbles/spinner.rb +122 -0
- data/lib/bubbles/stopwatch.rb +189 -0
- data/lib/bubbles/table.rb +248 -0
- data/lib/bubbles/text_area.rb +503 -0
- data/lib/bubbles/text_input.rb +543 -0
- data/lib/bubbles/timer.rb +196 -0
- data/lib/bubbles/version.rb +4 -1
- data/lib/bubbles/viewport.rb +283 -0
- data/lib/bubbles.rb +20 -35
- data/sig/bubbles/ansi.rbs +23 -0
- data/sig/bubbles/cryptic_spinner.rbs +143 -0
- data/sig/bubbles/cursor.rbs +87 -0
- data/sig/bubbles/file_picker.rbs +138 -0
- data/sig/bubbles/help.rbs +85 -0
- data/sig/bubbles/key.rbs +63 -0
- data/sig/bubbles/list.rbs +138 -0
- data/sig/bubbles/paginator.rbs +90 -0
- data/sig/bubbles/progress.rbs +123 -0
- data/sig/bubbles/spinner/spinners.rbs +32 -0
- data/sig/bubbles/spinner.rbs +74 -0
- data/sig/bubbles/stopwatch.rbs +97 -0
- data/sig/bubbles/table.rbs +119 -0
- data/sig/bubbles/text_area.rbs +161 -0
- data/sig/bubbles/text_input.rbs +183 -0
- data/sig/bubbles/timer.rbs +107 -0
- data/sig/bubbles/version.rbs +5 -0
- data/sig/bubbles/viewport.rbs +119 -0
- data/sig/bubbles.rbs +4 -0
- metadata +70 -67
- data/.gitignore +0 -14
- data/.rspec +0 -2
- data/.travis.yml +0 -10
- data/Gemfile +0 -4
- data/LICENSE +0 -20
- data/Rakefile +0 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/exe/bubbles +0 -5
- data/lib/bubbles/bubblicious_file.rb +0 -42
- data/lib/bubbles/command_queue.rb +0 -43
- data/lib/bubbles/common_uploader_interface.rb +0 -13
- data/lib/bubbles/config.rb +0 -149
- data/lib/bubbles/dir_watcher.rb +0 -53
- data/lib/bubbles/uploaders/local_dir.rb +0 -39
- data/lib/bubbles/uploaders/s3.rb +0 -36
- data/lib/bubbles/uploaders/s3_ensure_connection.rb +0 -26
- data/tmp/dummy_local_dir_uploader_dir/.gitkeep +0 -0
- data/tmp/dummy_processing_dir/.gitkeep +0 -0
data/README.md
CHANGED
|
@@ -1,138 +1,659 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>Bubbles for Ruby</h1>
|
|
3
|
+
<h4>TUI components for Bubble Tea</h4>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://rubygems.org/gems/bubbles"><img alt="Gem Version" src="https://img.shields.io/gem/v/bubbles"></a>
|
|
7
|
+
<a href="https://github.com/marcoroth/bubbles-ruby/blob/main/LICENSE.txt"><img alt="License" src="https://img.shields.io/github/license/marcoroth/bubbles-ruby"></a>
|
|
8
|
+
</p>
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
<p>Ruby implementation of <a href="https://github.com/charmbracelet/bubbles">charmbracelet/bubbles</a>.<br/>Common UI components for building terminal applications with <a href="https://github.com/marcoroth/bubbletea-ruby">Bubble Tea</a>.</p>
|
|
11
|
+
</div>
|
|
7
12
|
|
|
13
|
+
## Installation
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
**Add to your Gemfile:**
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
```ruby
|
|
18
|
+
gem "bubbles"
|
|
19
|
+
```
|
|
12
20
|
|
|
21
|
+
**Or install directly:**
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
```bash
|
|
24
|
+
gem install bubbles
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Components
|
|
28
|
+
|
|
29
|
+
| Component | Description |
|
|
30
|
+
|-----------|-------------|
|
|
31
|
+
| [Spinner](#spinner) | Loading spinners with multiple styles |
|
|
32
|
+
| [CrypticSpinner](#crypticspinner) | Animated gradient spinner with cryptic characters |
|
|
33
|
+
| [Progress](#progress) | Animated progress bars |
|
|
34
|
+
| [Timer](#timer) | Countdown timer |
|
|
35
|
+
| [Stopwatch](#stopwatch) | Elapsed time counter |
|
|
36
|
+
| [TextInput](#textinput) | Single-line text input with cursor |
|
|
37
|
+
| [TextArea](#textarea) | Multi-line text input |
|
|
38
|
+
| [Viewport](#viewport) | Scrollable content pane |
|
|
39
|
+
| [List](#list) | Interactive list with filtering |
|
|
40
|
+
| [Table](#table) | Data table with columns |
|
|
41
|
+
| [FilePicker](#filepicker) | File and directory browser |
|
|
42
|
+
| [Paginator](#paginator) | Pagination controls |
|
|
43
|
+
| [Help](#help) | Help text generator |
|
|
44
|
+
| [Key](#key) | Key binding definitions |
|
|
45
|
+
| [Cursor](#cursor) | Blinking cursor for inputs |
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
15
48
|
|
|
16
|
-
|
|
49
|
+
### Spinner
|
|
50
|
+
|
|
51
|
+
**Animated loading indicator:**
|
|
17
52
|
|
|
18
53
|
```ruby
|
|
19
|
-
|
|
54
|
+
require "bubbles"
|
|
55
|
+
|
|
56
|
+
spinner = Bubbles::Spinner.new
|
|
57
|
+
spinner.spinner = Bubbles::Spinners::DOT
|
|
20
58
|
```
|
|
21
59
|
|
|
22
|
-
|
|
60
|
+
**In your update method:**
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
spinner, command = spinner.update(message)
|
|
64
|
+
```
|
|
23
65
|
|
|
24
|
-
|
|
66
|
+
**In your view method:**
|
|
25
67
|
|
|
26
|
-
|
|
68
|
+
```ruby
|
|
69
|
+
spinner.view
|
|
70
|
+
```
|
|
27
71
|
|
|
28
|
-
|
|
72
|
+
**Available spinner styles:**
|
|
29
73
|
|
|
30
|
-
|
|
74
|
+
```ruby
|
|
75
|
+
Bubbles::Spinners::LINE
|
|
76
|
+
Bubbles::Spinners::DOT
|
|
77
|
+
Bubbles::Spinners::MINI_DOT
|
|
78
|
+
Bubbles::Spinners::JUMP
|
|
79
|
+
Bubbles::Spinners::PULSE
|
|
80
|
+
Bubbles::Spinners::POINTS
|
|
81
|
+
Bubbles::Spinners::GLOBE
|
|
82
|
+
Bubbles::Spinners::MOON
|
|
83
|
+
Bubbles::Spinners::MONKEY
|
|
84
|
+
Bubbles::Spinners::METER
|
|
85
|
+
Bubbles::Spinners::HAMBURGER
|
|
86
|
+
Bubbles::Spinners::ELLIPSIS
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### CrypticSpinner
|
|
90
|
+
|
|
91
|
+
**Animated gradient spinner with cryptic characters (inspired by [Charm CLI](https://github.com/charmbracelet/crush)):**
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
spinner = Bubbles::CrypticSpinner.new(
|
|
95
|
+
size: 15,
|
|
96
|
+
label: "Thinking",
|
|
97
|
+
cycle_colors: true
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**In your update method:**
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
case message
|
|
105
|
+
when Bubbles::CrypticSpinner::TickMessage
|
|
106
|
+
spinner, command = spinner.update(message)
|
|
107
|
+
end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**In your view method:**
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
spinner.view
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Custom colors (using CharmTone palette):**
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
spinner = Bubbles::CrypticSpinner.new(
|
|
120
|
+
size: 15,
|
|
121
|
+
label: "Processing",
|
|
122
|
+
color_a: "#6B50FF", # Charple (purple)
|
|
123
|
+
color_b: "#FF60FF", # Dolly (pink)
|
|
124
|
+
label_color: "#DFDBDD",
|
|
125
|
+
cycle_colors: true
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Multi-row (matrix style):**
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
spinner = Bubbles::CrypticSpinner.new(
|
|
133
|
+
size: 40,
|
|
134
|
+
rows: 5,
|
|
135
|
+
label: "Decrypting",
|
|
136
|
+
color_a: "#00ff00",
|
|
137
|
+
color_b: "#003300",
|
|
138
|
+
cycle_colors: true
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Options:**
|
|
143
|
+
|
|
144
|
+
| Option | Default | Description |
|
|
145
|
+
|--------|---------|-------------|
|
|
146
|
+
| `size` | 10 | Number of cycling characters |
|
|
147
|
+
| `rows` | 1 | Number of rows (for matrix effect) |
|
|
148
|
+
| `label` | "" | Text label after the animation |
|
|
149
|
+
| `color_a` | "#6B50FF" | Start color of gradient |
|
|
150
|
+
| `color_b` | "#FF60FF" | End color of gradient |
|
|
151
|
+
| `label_color` | "#DFDBDD" | Color of the label text |
|
|
152
|
+
| `cycle_colors` | false | Animate gradient movement |
|
|
153
|
+
|
|
154
|
+
### Progress
|
|
155
|
+
|
|
156
|
+
**Animated progress bar:**
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
progress = Bubbles::Progress.new(width: 40)
|
|
160
|
+
progress.set_percent(0.5)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**In your view:**
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
progress.view
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Customization:**
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
progress = Bubbles::Progress.new(
|
|
173
|
+
width: 40,
|
|
174
|
+
full: "█",
|
|
175
|
+
empty: "░",
|
|
176
|
+
show_percentage: true
|
|
177
|
+
)
|
|
178
|
+
progress.full_color = "212"
|
|
179
|
+
progress.empty_color = "238"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Timer
|
|
183
|
+
|
|
184
|
+
**Countdown timer (60 seconds):**
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
timer = Bubbles::Timer.new(60)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Start the timer:**
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
command = timer.start
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**In update:**
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
timer, command = timer.update(message)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Check if done:**
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
timer.timed_out?
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**In view:**
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
timer.view
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Stopwatch
|
|
215
|
+
|
|
216
|
+
**Elapsed time counter:**
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
stopwatch = Bubbles::Stopwatch.new
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Start/stop/toggle:**
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
command = stopwatch.start
|
|
226
|
+
stopwatch.stop
|
|
227
|
+
command = stopwatch.toggle
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**In view:**
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
stopwatch.view
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### TextInput
|
|
237
|
+
|
|
238
|
+
**Single-line text input:**
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
input = Bubbles::TextInput.new
|
|
242
|
+
input.placeholder = "Enter your name..."
|
|
243
|
+
input.prompt = "> "
|
|
244
|
+
input.focus
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**In update:**
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
input, command = input.update(message)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Get value:**
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
input.value
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**In view:**
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
input.view
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Password mode:**
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
input.echo_mode = :password
|
|
269
|
+
input.echo_character = "*"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**With suggestions:**
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
input.suggestions = ["apple", "apricot", "avocado"]
|
|
276
|
+
input.show_suggestions = true
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### TextArea
|
|
280
|
+
|
|
281
|
+
**Multi-line text input:**
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
textarea = Bubbles::TextArea.new(width: 60, height: 10)
|
|
285
|
+
textarea.placeholder = "Type your message..."
|
|
286
|
+
textarea.show_line_numbers = true
|
|
287
|
+
textarea.focus
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**In update:**
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
textarea, command = textarea.update(message)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Get value:**
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
textarea.value
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Position info:**
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
textarea.row
|
|
306
|
+
textarea.col
|
|
307
|
+
textarea.line_count
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Viewport
|
|
311
|
+
|
|
312
|
+
**Scrollable content pane:**
|
|
31
313
|
|
|
32
|
-
|
|
314
|
+
```ruby
|
|
315
|
+
viewport = Bubbles::Viewport.new(width: 80, height: 20)
|
|
316
|
+
viewport.content = long_text
|
|
317
|
+
```
|
|
33
318
|
|
|
34
|
-
|
|
35
|
-
|
|
319
|
+
**In update (handles scroll keys):**
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
viewport, command = viewport.update(message)
|
|
323
|
+
```
|
|
36
324
|
|
|
37
|
-
|
|
38
|
-
> can read that file
|
|
325
|
+
**Scroll info:**
|
|
39
326
|
|
|
40
|
-
|
|
327
|
+
```ruby
|
|
328
|
+
viewport.scroll_percent
|
|
329
|
+
viewport.at_top?
|
|
330
|
+
viewport.at_bottom?
|
|
331
|
+
```
|
|
41
332
|
|
|
42
|
-
|
|
43
|
-
s3_access_key_id: xxxxxxxxxxxxxxxxxxxx
|
|
44
|
-
s3_secret_access_key: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
|
45
|
-
s3_bucket: bucket_name
|
|
46
|
-
s3_region: eu-west-1
|
|
333
|
+
**In view:**
|
|
47
334
|
|
|
48
|
-
|
|
49
|
-
|
|
335
|
+
```ruby
|
|
336
|
+
viewport.view
|
|
50
337
|
```
|
|
51
338
|
|
|
52
|
-
|
|
339
|
+
**Programmatic scrolling:**
|
|
53
340
|
|
|
54
|
-
|
|
341
|
+
```ruby
|
|
342
|
+
viewport.scroll_down(5)
|
|
343
|
+
viewport.scroll_up(5)
|
|
344
|
+
viewport.page_down
|
|
345
|
+
viewport.page_up
|
|
346
|
+
viewport.goto_top
|
|
347
|
+
viewport.goto_bottom
|
|
348
|
+
```
|
|
55
349
|
|
|
56
|
-
|
|
350
|
+
### List
|
|
57
351
|
|
|
352
|
+
**Interactive list with filtering:**
|
|
58
353
|
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
|
|
354
|
+
```ruby
|
|
355
|
+
items = [
|
|
356
|
+
{ title: "Item 1", description: "First item" },
|
|
357
|
+
{ title: "Item 2", description: "Second item" }
|
|
358
|
+
]
|
|
62
359
|
|
|
63
|
-
|
|
64
|
-
|
|
360
|
+
list = Bubbles::List.new(items, width: 40, height: 10)
|
|
361
|
+
list.title = "My List"
|
|
362
|
+
```
|
|
65
363
|
|
|
66
|
-
|
|
67
|
-
num_of_files_to_schedule: 1 # how many files schedule for processing at the same time
|
|
364
|
+
**In update:**
|
|
68
365
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- 'Bubbles::Uploaders::LocalDir'
|
|
366
|
+
```ruby
|
|
367
|
+
list, command = list.update(message)
|
|
368
|
+
```
|
|
73
369
|
|
|
74
|
-
|
|
75
|
-
s3_secret_access_key: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
|
76
|
-
s3_bucket: bucket_name
|
|
77
|
-
s3_region: eu-west-1
|
|
78
|
-
s3_path: foo_folder/bar_folder/car_folder # will upload to s3://bucket/foo_folder/bar_folder/car_folder
|
|
79
|
-
s3_acl: private # # accepts private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control
|
|
370
|
+
**Get selection:**
|
|
80
371
|
|
|
81
|
-
|
|
82
|
-
|
|
372
|
+
```ruby
|
|
373
|
+
list.selected_item
|
|
83
374
|
```
|
|
84
375
|
|
|
85
|
-
|
|
376
|
+
**Filter state:**
|
|
86
377
|
|
|
87
|
-
|
|
378
|
+
```ruby
|
|
379
|
+
list.filter_state
|
|
380
|
+
```
|
|
88
381
|
|
|
89
|
-
|
|
90
|
-
`Bubbles::Config.new` instance and set all the options you want there:
|
|
382
|
+
**Styling:**
|
|
91
383
|
|
|
92
384
|
```ruby
|
|
93
|
-
|
|
94
|
-
|
|
385
|
+
list.title_style = Lipgloss::Style.new.bold(true).foreground("212")
|
|
386
|
+
list.selected_item_style = Lipgloss::Style.new.foreground("212")
|
|
387
|
+
list.item_style = Lipgloss::Style.new.foreground("252")
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Table
|
|
391
|
+
|
|
392
|
+
**Data table with columns:**
|
|
95
393
|
|
|
96
|
-
|
|
394
|
+
```ruby
|
|
395
|
+
columns = [
|
|
396
|
+
{ title: "Name", width: 20 },
|
|
397
|
+
{ title: "Age", width: 5 },
|
|
398
|
+
{ title: "City", width: 15 }
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
rows = [
|
|
402
|
+
["Alice", "30", "New York"],
|
|
403
|
+
["Bob", "25", "London"]
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
table = Bubbles::Table.new(columns: columns, rows: rows, height: 10)
|
|
407
|
+
```
|
|
97
408
|
|
|
98
|
-
|
|
99
|
-
c.source_dir = /var/myuser/source_folder
|
|
100
|
-
c.processing_dir = /var/myuser/processing_folder
|
|
101
|
-
c.uploader_classes = [Bubbles::Uploaders::S3, Bubbles::Uploaders::LocalDir]
|
|
102
|
-
c.local_dir_uploader_path = /mnt/network_smb_storage
|
|
103
|
-
c.s3_region = 'eu-west-1'
|
|
104
|
-
c.s3_bucket = 'mybckt'
|
|
105
|
-
c.s3_access_key_id = 'xxxxxxxxxxx'
|
|
106
|
-
c.s3_secret_access_key = 'yyyyyyyyyyy'
|
|
409
|
+
**In update:**
|
|
107
410
|
|
|
108
|
-
|
|
411
|
+
```ruby
|
|
412
|
+
table, command = table.update(message)
|
|
413
|
+
```
|
|
109
414
|
|
|
110
|
-
|
|
111
|
-
command_queue << Bubbles::DirWatcher.new(config: c, command_queue: command_queue)
|
|
415
|
+
**Get selection:**
|
|
112
416
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
417
|
+
```ruby
|
|
418
|
+
table.selected_row
|
|
419
|
+
table.selected_row_data
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Styling:**
|
|
423
|
+
|
|
424
|
+
```ruby
|
|
425
|
+
table.header_style = Lipgloss::Style.new.bold(true).foreground("212")
|
|
426
|
+
table.cell_style = Lipgloss::Style.new.padding_left(1)
|
|
427
|
+
table.selected_style = Lipgloss::Style.new.bold(true).background("57")
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### FilePicker
|
|
431
|
+
|
|
432
|
+
**File and directory browser:**
|
|
433
|
+
|
|
434
|
+
```ruby
|
|
435
|
+
picker = Bubbles::FilePicker.new(directory: ".")
|
|
436
|
+
picker.height = 15
|
|
437
|
+
picker.show_hidden = false
|
|
438
|
+
picker.allowed_types = ["rb", "txt"]
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**In update:**
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
picker, command = picker.update(message)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Check for selection:**
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
if picker.did_select_file?
|
|
451
|
+
selected_path = picker.path
|
|
116
452
|
end
|
|
117
453
|
```
|
|
118
454
|
|
|
119
|
-
|
|
455
|
+
**Options:**
|
|
456
|
+
|
|
457
|
+
```ruby
|
|
458
|
+
picker.show_permissions = true
|
|
459
|
+
picker.show_size = true
|
|
460
|
+
picker.dir_allowed = false
|
|
461
|
+
picker.file_allowed = true
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Paginator
|
|
120
465
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
466
|
+
**Pagination controls:**
|
|
467
|
+
|
|
468
|
+
```ruby
|
|
469
|
+
paginator = Bubbles::Paginator.new(type: Bubbles::Paginator::DOTS)
|
|
470
|
+
paginator.per_page = 10
|
|
471
|
+
paginator.update_total_pages(100)
|
|
125
472
|
```
|
|
126
473
|
|
|
127
|
-
|
|
474
|
+
**Navigation:**
|
|
475
|
+
|
|
476
|
+
```ruby
|
|
477
|
+
paginator.next_page
|
|
478
|
+
paginator.prev_page
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Get slice bounds for your data:**
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
start_index, end_index = paginator.slice_bounds(items.length)
|
|
485
|
+
visible_items = items[start_index...end_index]
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**In view:**
|
|
489
|
+
|
|
490
|
+
```ruby
|
|
491
|
+
paginator.view
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Types:**
|
|
495
|
+
|
|
496
|
+
```ruby
|
|
497
|
+
Bubbles::Paginator::ARABIC
|
|
498
|
+
Bubbles::Paginator::DOTS
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Help
|
|
502
|
+
|
|
503
|
+
**Help text generator:**
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
help = Bubbles::Help.new
|
|
507
|
+
|
|
508
|
+
bindings = [
|
|
509
|
+
Bubbles::Key.binding(keys: ["up", "k"], help: ["↑/k", "up"]),
|
|
510
|
+
Bubbles::Key.binding(keys: ["down", "j"], help: ["↓/j", "down"]),
|
|
511
|
+
Bubbles::Key.binding(keys: ["q"], help: ["q", "quit"])
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
help.short_help_view(bindings)
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Key
|
|
518
|
+
|
|
519
|
+
**Key binding definitions:**
|
|
520
|
+
|
|
521
|
+
```ruby
|
|
522
|
+
quit_binding = Bubbles::Key.binding(
|
|
523
|
+
keys: ["q", "ctrl+c"],
|
|
524
|
+
help: ["q", "quit"]
|
|
525
|
+
)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
**Check if a key matches:**
|
|
529
|
+
|
|
530
|
+
```ruby
|
|
531
|
+
Bubbles::Key.matches?(message, quit_binding)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Cursor
|
|
535
|
+
|
|
536
|
+
**Blinking cursor for inputs:**
|
|
537
|
+
|
|
538
|
+
```ruby
|
|
539
|
+
cursor = Bubbles::Cursor.new
|
|
540
|
+
cursor.char = "_"
|
|
541
|
+
cursor.focus
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Set cursor mode:**
|
|
545
|
+
|
|
546
|
+
```ruby
|
|
547
|
+
cursor.set_mode(:blink)
|
|
548
|
+
cursor.set_mode(:static)
|
|
549
|
+
cursor.set_mode(:hide)
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**In update:**
|
|
553
|
+
|
|
554
|
+
```ruby
|
|
555
|
+
cursor, command = cursor.update(message)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**In view:**
|
|
559
|
+
|
|
560
|
+
```ruby
|
|
561
|
+
cursor.view
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Complete Example
|
|
565
|
+
|
|
566
|
+
```ruby
|
|
567
|
+
require "bubbletea"
|
|
568
|
+
require "lipgloss"
|
|
569
|
+
require "bubbles"
|
|
570
|
+
|
|
571
|
+
class MyApp
|
|
572
|
+
include Bubbletea::Model
|
|
573
|
+
|
|
574
|
+
def initialize
|
|
575
|
+
@spinner = Bubbles::Spinner.new
|
|
576
|
+
@spinner.spinner = Bubbles::Spinners::DOT
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
def init
|
|
580
|
+
[self, @spinner.tick]
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def update(message)
|
|
584
|
+
case message
|
|
585
|
+
when Bubbletea::KeyMessage
|
|
586
|
+
return [self, Bubbletea.quit] if message.to_s == "q"
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
@spinner, command = @spinner.update(message)
|
|
590
|
+
[self, command]
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def view
|
|
594
|
+
"#{@spinner.view} Loading...\n\nPress q to quit"
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
Bubbletea.run(MyApp.new)
|
|
599
|
+
```
|
|
128
600
|
|
|
129
601
|
## Development
|
|
130
602
|
|
|
131
|
-
|
|
132
|
-
|
|
603
|
+
**Requirements:**
|
|
604
|
+
- Ruby 3.2+
|
|
605
|
+
- [bubbletea-ruby](https://github.com/marcoroth/bubbletea-ruby)
|
|
606
|
+
- [lipgloss-ruby](https://github.com/marcoroth/lipgloss-ruby) (optional, for styling)
|
|
607
|
+
|
|
608
|
+
**Install dependencies:**
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
bundle install
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Run tests:**
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
bundle exec rake test
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**Run demos:**
|
|
621
|
+
|
|
622
|
+
```bash
|
|
623
|
+
./demo/spinner
|
|
624
|
+
./demo/cryptic_spinner
|
|
625
|
+
./demo/progress
|
|
626
|
+
./demo/textinput
|
|
627
|
+
./demo/textarea
|
|
628
|
+
./demo/viewport
|
|
629
|
+
./demo/list
|
|
630
|
+
./demo/table
|
|
631
|
+
./demo/filepicker
|
|
632
|
+
./demo/timer
|
|
633
|
+
./demo/stopwatch
|
|
634
|
+
./demo/paginator
|
|
635
|
+
./demo/help
|
|
636
|
+
./demo/cursor
|
|
637
|
+
```
|
|
133
638
|
|
|
134
639
|
## Contributing
|
|
135
640
|
|
|
136
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
641
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/bubbles-ruby.
|
|
642
|
+
|
|
643
|
+
## License
|
|
644
|
+
|
|
645
|
+
The gem is available as open source under the terms of the MIT License.
|
|
646
|
+
|
|
647
|
+
## Acknowledgments
|
|
648
|
+
|
|
649
|
+
This gem is a Ruby implementation of [charmbracelet/bubbles](https://github.com/charmbracelet/bubbles), part of the excellent [Charm](https://charm.sh) ecosystem. Charm Ruby is not affiliated with or endorsed by Charmbracelet, Inc.
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
Part of [Charm Ruby](https://charm-ruby.dev).
|
|
654
|
+
|
|
655
|
+
<a href="https://charm-ruby.dev"><img alt="Charm Ruby" src="https://marcoroth.dev/images/heros/glamorous-christmas.png" width="400"></a>
|
|
137
656
|
|
|
657
|
+
[Lipgloss](https://github.com/marcoroth/lipgloss-ruby) • [Bubble Tea](https://github.com/marcoroth/bubbletea-ruby) • [Bubbles](https://github.com/marcoroth/bubbles-ruby) • [Glamour](https://github.com/marcoroth/glamour-ruby) • [Huh?](https://github.com/marcoroth/huh-ruby) • [Harmonica](https://github.com/marcoroth/harmonica-ruby) • [Bubblezone](https://github.com/marcoroth/bubblezone-ruby) • [Gum](https://github.com/marcoroth/gum-ruby) • [ntcharts](https://github.com/marcoroth/ntcharts-ruby)
|
|
138
658
|
|
|
659
|
+
The terminal doesn't have to be boring.
|