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.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +21 -0
  3. data/README.md +601 -80
  4. data/bubbles.gemspec +29 -21
  5. data/lib/bubbles/ansi.rb +71 -0
  6. data/lib/bubbles/cryptic_spinner.rb +274 -0
  7. data/lib/bubbles/cursor.rb +169 -0
  8. data/lib/bubbles/file_picker.rb +397 -0
  9. data/lib/bubbles/help.rb +165 -0
  10. data/lib/bubbles/key.rb +96 -0
  11. data/lib/bubbles/list.rb +365 -0
  12. data/lib/bubbles/paginator.rb +158 -0
  13. data/lib/bubbles/progress.rb +276 -0
  14. data/lib/bubbles/spinner/spinners.rb +77 -0
  15. data/lib/bubbles/spinner.rb +122 -0
  16. data/lib/bubbles/stopwatch.rb +189 -0
  17. data/lib/bubbles/table.rb +248 -0
  18. data/lib/bubbles/text_area.rb +503 -0
  19. data/lib/bubbles/text_input.rb +543 -0
  20. data/lib/bubbles/timer.rb +196 -0
  21. data/lib/bubbles/version.rb +4 -1
  22. data/lib/bubbles/viewport.rb +283 -0
  23. data/lib/bubbles.rb +20 -35
  24. data/sig/bubbles/ansi.rbs +23 -0
  25. data/sig/bubbles/cryptic_spinner.rbs +143 -0
  26. data/sig/bubbles/cursor.rbs +87 -0
  27. data/sig/bubbles/file_picker.rbs +138 -0
  28. data/sig/bubbles/help.rbs +85 -0
  29. data/sig/bubbles/key.rbs +63 -0
  30. data/sig/bubbles/list.rbs +138 -0
  31. data/sig/bubbles/paginator.rbs +90 -0
  32. data/sig/bubbles/progress.rbs +123 -0
  33. data/sig/bubbles/spinner/spinners.rbs +32 -0
  34. data/sig/bubbles/spinner.rbs +74 -0
  35. data/sig/bubbles/stopwatch.rbs +97 -0
  36. data/sig/bubbles/table.rbs +119 -0
  37. data/sig/bubbles/text_area.rbs +161 -0
  38. data/sig/bubbles/text_input.rbs +183 -0
  39. data/sig/bubbles/timer.rbs +107 -0
  40. data/sig/bubbles/version.rbs +5 -0
  41. data/sig/bubbles/viewport.rbs +119 -0
  42. data/sig/bubbles.rbs +4 -0
  43. metadata +70 -67
  44. data/.gitignore +0 -14
  45. data/.rspec +0 -2
  46. data/.travis.yml +0 -10
  47. data/Gemfile +0 -4
  48. data/LICENSE +0 -20
  49. data/Rakefile +0 -6
  50. data/bin/console +0 -14
  51. data/bin/setup +0 -8
  52. data/exe/bubbles +0 -5
  53. data/lib/bubbles/bubblicious_file.rb +0 -42
  54. data/lib/bubbles/command_queue.rb +0 -43
  55. data/lib/bubbles/common_uploader_interface.rb +0 -13
  56. data/lib/bubbles/config.rb +0 -149
  57. data/lib/bubbles/dir_watcher.rb +0 -53
  58. data/lib/bubbles/uploaders/local_dir.rb +0 -39
  59. data/lib/bubbles/uploaders/s3.rb +0 -36
  60. data/lib/bubbles/uploaders/s3_ensure_connection.rb +0 -26
  61. data/tmp/dummy_local_dir_uploader_dir/.gitkeep +0 -0
  62. data/tmp/dummy_processing_dir/.gitkeep +0 -0
data/README.md CHANGED
@@ -1,138 +1,659 @@
1
- # Bubbles
1
+ <div align="center">
2
+ <h1>Bubbles for Ruby</h1>
3
+ <h4>TUI components for Bubble Tea</h4>
2
4
 
3
- [![Build Status](https://travis-ci.org/equivalent/bubbles.svg?branch=master)](https://travis-ci.org/equivalent/bubbles)
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
- Daemonized file uploader that watch a folder and uploads any files files
6
- to AWS S3 or Local directory (e.g. mounted NAS volume). Designed for Raspberry pi zero
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
- Notes:
15
+ **Add to your Gemfile:**
10
16
 
11
- * for AWS S3 upload we use AWS-SDK [s3 put_object](http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#put_object-instance_method) the single file upload should not exceed 5GB. I'm not planing to introduce S3 multipart uploads but Pull Requests are welcome.
17
+ ```ruby
18
+ gem "bubbles"
19
+ ```
12
20
 
21
+ **Or install directly:**
13
22
 
14
- ## Installation
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
- Add this line to your application's Gemfile:
49
+ ### Spinner
50
+
51
+ **Animated loading indicator:**
17
52
 
18
53
  ```ruby
19
- gem 'bubbles'
54
+ require "bubbles"
55
+
56
+ spinner = Bubbles::Spinner.new
57
+ spinner.spinner = Bubbles::Spinners::DOT
20
58
  ```
21
59
 
22
- And then execute:
60
+ **In your update method:**
61
+
62
+ ```ruby
63
+ spinner, command = spinner.update(message)
64
+ ```
23
65
 
24
- $ bundle
66
+ **In your view method:**
25
67
 
26
- Or install it yourself as:
68
+ ```ruby
69
+ spinner.view
70
+ ```
27
71
 
28
- $ gem install bubbles
72
+ **Available spinner styles:**
29
73
 
30
- ## Usage
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
- ##### The easy way
314
+ ```ruby
315
+ viewport = Bubbles::Viewport.new(width: 80, height: 20)
316
+ viewport.content = long_text
317
+ ```
33
318
 
34
- Gem by default looks into to two locations for configuration file. So
35
- either create `~/.bubbles/config` or `/var/lib/bubbles/config.yml`
319
+ **In update (handles scroll keys):**
320
+
321
+ ```ruby
322
+ viewport, command = viewport.update(message)
323
+ ```
36
324
 
37
- > note: make sure you have correct read access permission so that ruby
38
- > can read that file
325
+ **Scroll info:**
39
326
 
40
- ...with content similar to this:
327
+ ```ruby
328
+ viewport.scroll_percent
329
+ viewport.at_top?
330
+ viewport.at_bottom?
331
+ ```
41
332
 
42
- ```yml
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
- source_dir: /home/myuser/source_folder
49
- processing_dir: /home/myuser/processing_folder
335
+ ```ruby
336
+ viewport.view
50
337
  ```
51
338
 
52
- Now all is left is to lunch bubbles with `bubbles` or `bundle exec bubbles`
339
+ **Programmatic scrolling:**
53
340
 
54
- > note: files in `source_folder` will get removed after successful upload
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
- ##### More advanced config
350
+ ### List
57
351
 
352
+ **Interactive list with filtering:**
58
353
 
59
- ```yml
60
- source_dir: /var/myuser/source_folder # source from where to pick up files
61
- processing_dir: /home/myuser/processing_folder
354
+ ```ruby
355
+ items = [
356
+ { title: "Item 1", description: "First item" },
357
+ { title: "Item 2", description: "Second item" }
358
+ ]
62
359
 
63
- log_level: 0 # debug log level
64
- log_path: /var/log/bubbles.log # default is STDOOT
360
+ list = Bubbles::List.new(items, width: 40, height: 10)
361
+ list.title = "My List"
362
+ ```
65
363
 
66
- sleep_interval: 1 # sleep between next command
67
- num_of_files_to_schedule: 1 # how many files schedule for processing at the same time
364
+ **In update:**
68
365
 
69
- uploaders:
70
- - 'Bubbles::Uploaders::S3EnsureConnection' # this will check if s3 connection can be established
71
- - 'Bubbles::Uploaders::S3' # by default only S3 uploader is used
72
- - 'Bubbles::Uploaders::LocalDir'
366
+ ```ruby
367
+ list, command = list.update(message)
368
+ ```
73
369
 
74
- s3_access_key_id: xxxxxxxxxxxxxxxxxxxx
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
- local_dir_uploader_path: /mnt/network_smb_storage/
82
- local_dir_metadata_file_path: /var/log/uploads_metadata.yaml
372
+ ```ruby
373
+ list.selected_item
83
374
  ```
84
375
 
85
- > Look into [lib/bubbles/config.rb](https://github.com/equivalent/bubbles/blob/master/lib/bubbles/config.rb) for more details.
376
+ **Filter state:**
86
377
 
87
- ##### Full Ruby way
378
+ ```ruby
379
+ list.filter_state
380
+ ```
88
381
 
89
- You can create custom ruby file and you initialize custom
90
- `Bubbles::Config.new` instance and set all the options you want there:
382
+ **Styling:**
91
383
 
92
384
  ```ruby
93
- # ~/my_bubbles_runner.rb
94
- require 'bubbles'
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
- c = Bubbles::Config.new
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
- c.log_level = 0
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
- command_queue = Bubbles::CommandQueue.new(config: c)
111
- command_queue << Bubbles::DirWatcher.new(config: c, command_queue: command_queue)
415
+ **Get selection:**
112
416
 
113
- loop do
114
- command_queue.call_next
115
- sleep c.sleep_interval
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
- ..and execute:
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
- ```sh
122
- ruby my_bubbles_runner.rb
123
- # or
124
- bundle exec ruby my_bubbles_runner.rb
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
- > Look into [lib/bubbles/config.rb](https://github.com/equivalent/bubbles/blob/master/lib/bubbles/config.rb) for more details.
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
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
132
- To install this gem onto your local machine, run `bundle exec rake install`.
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/equivalent/bubbles.
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.