bubbles 0.0.5 → 0.1.0

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 (58) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +21 -0
  3. data/README.md +524 -80
  4. data/bubbles.gemspec +29 -21
  5. data/lib/bubbles/cursor.rb +169 -0
  6. data/lib/bubbles/file_picker.rb +397 -0
  7. data/lib/bubbles/help.rb +170 -0
  8. data/lib/bubbles/key.rb +96 -0
  9. data/lib/bubbles/list.rb +365 -0
  10. data/lib/bubbles/paginator.rb +158 -0
  11. data/lib/bubbles/progress.rb +276 -0
  12. data/lib/bubbles/spinner/spinners.rb +77 -0
  13. data/lib/bubbles/spinner.rb +122 -0
  14. data/lib/bubbles/stopwatch.rb +189 -0
  15. data/lib/bubbles/table.rb +248 -0
  16. data/lib/bubbles/text_area.rb +503 -0
  17. data/lib/bubbles/text_input.rb +543 -0
  18. data/lib/bubbles/timer.rb +196 -0
  19. data/lib/bubbles/version.rb +4 -1
  20. data/lib/bubbles/viewport.rb +296 -0
  21. data/lib/bubbles.rb +18 -35
  22. data/sig/bubbles/cursor.rbs +87 -0
  23. data/sig/bubbles/file_picker.rbs +138 -0
  24. data/sig/bubbles/help.rbs +88 -0
  25. data/sig/bubbles/key.rbs +63 -0
  26. data/sig/bubbles/list.rbs +138 -0
  27. data/sig/bubbles/paginator.rbs +90 -0
  28. data/sig/bubbles/progress.rbs +123 -0
  29. data/sig/bubbles/spinner/spinners.rbs +32 -0
  30. data/sig/bubbles/spinner.rbs +74 -0
  31. data/sig/bubbles/stopwatch.rbs +97 -0
  32. data/sig/bubbles/table.rbs +119 -0
  33. data/sig/bubbles/text_area.rbs +161 -0
  34. data/sig/bubbles/text_input.rbs +183 -0
  35. data/sig/bubbles/timer.rbs +107 -0
  36. data/sig/bubbles/version.rbs +5 -0
  37. data/sig/bubbles/viewport.rbs +125 -0
  38. data/sig/bubbles.rbs +4 -0
  39. metadata +66 -67
  40. data/.gitignore +0 -14
  41. data/.rspec +0 -2
  42. data/.travis.yml +0 -10
  43. data/Gemfile +0 -4
  44. data/LICENSE +0 -20
  45. data/Rakefile +0 -6
  46. data/bin/console +0 -14
  47. data/bin/setup +0 -8
  48. data/exe/bubbles +0 -5
  49. data/lib/bubbles/bubblicious_file.rb +0 -42
  50. data/lib/bubbles/command_queue.rb +0 -43
  51. data/lib/bubbles/common_uploader_interface.rb +0 -13
  52. data/lib/bubbles/config.rb +0 -149
  53. data/lib/bubbles/dir_watcher.rb +0 -53
  54. data/lib/bubbles/uploaders/local_dir.rb +0 -39
  55. data/lib/bubbles/uploaders/s3.rb +0 -36
  56. data/lib/bubbles/uploaders/s3_ensure_connection.rb +0 -26
  57. data/tmp/dummy_local_dir_uploader_dir/.gitkeep +0 -0
  58. data/tmp/dummy_processing_dir/.gitkeep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 268469699924c6a01e8093ac8213509c95cfcdc8
4
- data.tar.gz: 5caf988b1600e68ce86a37a7f3ba04ad89b2a912
2
+ SHA256:
3
+ metadata.gz: d64710274a0d2e55de7efb197eee82f4e8506cd47b345baffecda3f45c08958d
4
+ data.tar.gz: bacbc6bbbe38cf7db3bcd7c2492bccfb2965f472e1f4c2d1e27c80d492a3834a
5
5
  SHA512:
6
- metadata.gz: cd4b0257ecf635009040d9bafe01acd81793dc96391023011b7239f7d80260958a1c24fd938050128888a4ad59cee80a05d4cb661aba26ea44c1b36d7703bc58
7
- data.tar.gz: c305cd6f10835ec81fcfac0fcec0794e6d083d118d4106a01dc3a092225bdad1f34f7fc8d8bc2bfdac007a8e441b496a7d510b7c58b90665f0a1ac7ab66bcb55
6
+ metadata.gz: 9c8e0205d20ca9ebc1a7c3e18945b9e7d652200e27927d9292bca08a94716b85438fd8681b0efdbb5e099799457c56eb3cc19506b2922511000b6c228c0a5b11
7
+ data.tar.gz: 41ee83520438881adb482b5386b7cc6908d82bb4164829ca1a09f089a8ec420f3115264cefe73d4ac3f8e692d2fa4d5b4613d9d371a68b3496700e0973aa4bc1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Marco Roth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,138 +1,582 @@
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
+ | [Progress](#progress) | Animated progress bars |
33
+ | [Timer](#timer) | Countdown timer |
34
+ | [Stopwatch](#stopwatch) | Elapsed time counter |
35
+ | [TextInput](#textinput) | Single-line text input with cursor |
36
+ | [TextArea](#textarea) | Multi-line text input |
37
+ | [Viewport](#viewport) | Scrollable content pane |
38
+ | [List](#list) | Interactive list with filtering |
39
+ | [Table](#table) | Data table with columns |
40
+ | [FilePicker](#filepicker) | File and directory browser |
41
+ | [Paginator](#paginator) | Pagination controls |
42
+ | [Help](#help) | Help text generator |
43
+ | [Key](#key) | Key binding definitions |
44
+ | [Cursor](#cursor) | Blinking cursor for inputs |
45
+
46
+ ## Usage
47
+
48
+ ### Spinner
49
+
50
+ **Animated loading indicator:**
51
+
52
+ ```ruby
53
+ require "bubbles"
54
+
55
+ spinner = Bubbles::Spinner.new
56
+ spinner.spinner = Bubbles::Spinners::DOTS
57
+ ```
15
58
 
16
- Add this line to your application's Gemfile:
59
+ **In your update method:**
17
60
 
18
61
  ```ruby
19
- gem 'bubbles'
62
+ spinner, command = spinner.update(message)
20
63
  ```
21
64
 
22
- And then execute:
65
+ **In your view method:**
23
66
 
24
- $ bundle
67
+ ```ruby
68
+ spinner.view
69
+ ```
25
70
 
26
- Or install it yourself as:
71
+ **Available spinner styles:**
27
72
 
28
- $ gem install bubbles
73
+ ```ruby
74
+ Bubbles::Spinners::LINE
75
+ Bubbles::Spinners::DOT
76
+ Bubbles::Spinners::MINI_DOT
77
+ Bubbles::Spinners::JUMP
78
+ Bubbles::Spinners::PULSE
79
+ Bubbles::Spinners::POINTS
80
+ Bubbles::Spinners::GLOBE
81
+ Bubbles::Spinners::MOON
82
+ Bubbles::Spinners::MONKEY
83
+ Bubbles::Spinners::METER
84
+ Bubbles::Spinners::HAMBURGER
85
+ Bubbles::Spinners::ELLIPSIS
86
+ ```
29
87
 
30
- ## Usage
88
+ ### Progress
89
+
90
+ **Animated progress bar:**
91
+
92
+ ```ruby
93
+ progress = Bubbles::Progress.new(width: 40)
94
+ progress.set_percent(0.5)
95
+ ```
96
+
97
+ **In your view:**
98
+
99
+ ```ruby
100
+ progress.view
101
+ ```
102
+
103
+ **Customization:**
31
104
 
32
- ##### The easy way
105
+ ```ruby
106
+ progress = Bubbles::Progress.new(
107
+ width: 40,
108
+ full: "█",
109
+ empty: "░",
110
+ show_percentage: true
111
+ )
112
+ progress.full_color = "212"
113
+ progress.empty_color = "238"
114
+ ```
33
115
 
34
- Gem by default looks into to two locations for configuration file. So
35
- either create `~/.bubbles/config` or `/var/lib/bubbles/config.yml`
116
+ ### Timer
36
117
 
37
- > note: make sure you have correct read access permission so that ruby
38
- > can read that file
118
+ **Countdown timer (60 seconds):**
39
119
 
40
- ...with content similar to this:
120
+ ```ruby
121
+ timer = Bubbles::Timer.new(60)
122
+ ```
123
+
124
+ **Start the timer:**
125
+
126
+ ```ruby
127
+ command = timer.start
128
+ ```
41
129
 
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
130
+ **In update:**
47
131
 
48
- source_dir: /home/myuser/source_folder
49
- processing_dir: /home/myuser/processing_folder
132
+ ```ruby
133
+ timer, command = timer.update(message)
50
134
  ```
51
135
 
52
- Now all is left is to lunch bubbles with `bubbles` or `bundle exec bubbles`
136
+ **Check if done:**
53
137
 
54
- > note: files in `source_folder` will get removed after successful upload
138
+ ```ruby
139
+ timer.timed_out?
140
+ ```
55
141
 
56
- ##### More advanced config
142
+ **In view:**
143
+
144
+ ```ruby
145
+ timer.view
146
+ ```
57
147
 
148
+ ### Stopwatch
58
149
 
59
- ```yml
60
- source_dir: /var/myuser/source_folder # source from where to pick up files
61
- processing_dir: /home/myuser/processing_folder
150
+ **Elapsed time counter:**
62
151
 
63
- log_level: 0 # debug log level
64
- log_path: /var/log/bubbles.log # default is STDOOT
152
+ ```ruby
153
+ stopwatch = Bubbles::Stopwatch.new
154
+ ```
65
155
 
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
156
+ **Start/stop/toggle:**
68
157
 
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'
158
+ ```ruby
159
+ command = stopwatch.start
160
+ stopwatch.stop
161
+ command = stopwatch.toggle
162
+ ```
73
163
 
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
164
+ **In view:**
80
165
 
81
- local_dir_uploader_path: /mnt/network_smb_storage/
82
- local_dir_metadata_file_path: /var/log/uploads_metadata.yaml
166
+ ```ruby
167
+ stopwatch.view
83
168
  ```
84
169
 
85
- > Look into [lib/bubbles/config.rb](https://github.com/equivalent/bubbles/blob/master/lib/bubbles/config.rb) for more details.
170
+ ### TextInput
171
+
172
+ **Single-line text input:**
86
173
 
87
- ##### Full Ruby way
174
+ ```ruby
175
+ input = Bubbles::TextInput.new
176
+ input.placeholder = "Enter your name..."
177
+ input.prompt = "> "
178
+ input.focus
179
+ ```
88
180
 
89
- You can create custom ruby file and you initialize custom
90
- `Bubbles::Config.new` instance and set all the options you want there:
181
+ **In update:**
91
182
 
92
183
  ```ruby
93
- # ~/my_bubbles_runner.rb
94
- require 'bubbles'
184
+ input, command = input.update(message)
185
+ ```
95
186
 
96
- c = Bubbles::Config.new
187
+ **Get value:**
97
188
 
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'
189
+ ```ruby
190
+ input.value
191
+ ```
107
192
 
108
- # ...
193
+ **In view:**
194
+
195
+ ```ruby
196
+ input.view
197
+ ```
109
198
 
110
- command_queue = Bubbles::CommandQueue.new(config: c)
111
- command_queue << Bubbles::DirWatcher.new(config: c, command_queue: command_queue)
199
+ **Password mode:**
200
+
201
+ ```ruby
202
+ input.echo_mode = :password
203
+ input.echo_character = "*"
204
+ ```
205
+
206
+ **With suggestions:**
207
+
208
+ ```ruby
209
+ input.suggestions = ["apple", "apricot", "avocado"]
210
+ input.show_suggestions = true
211
+ ```
212
+
213
+ ### TextArea
214
+
215
+ **Multi-line text input:**
216
+
217
+ ```ruby
218
+ textarea = Bubbles::TextArea.new(width: 60, height: 10)
219
+ textarea.placeholder = "Type your message..."
220
+ textarea.show_line_numbers = true
221
+ textarea.focus
222
+ ```
223
+
224
+ **In update:**
225
+
226
+ ```ruby
227
+ textarea, command = textarea.update(message)
228
+ ```
229
+
230
+ **Get value:**
231
+
232
+ ```ruby
233
+ textarea.value
234
+ ```
235
+
236
+ **Position info:**
237
+
238
+ ```ruby
239
+ textarea.row
240
+ textarea.col
241
+ textarea.line_count
242
+ ```
243
+
244
+ ### Viewport
245
+
246
+ **Scrollable content pane:**
247
+
248
+ ```ruby
249
+ viewport = Bubbles::Viewport.new(width: 80, height: 20)
250
+ viewport.content = long_text
251
+ ```
252
+
253
+ **In update (handles scroll keys):**
254
+
255
+ ```ruby
256
+ viewport, command = viewport.update(message)
257
+ ```
258
+
259
+ **Scroll info:**
260
+
261
+ ```ruby
262
+ viewport.scroll_percent
263
+ viewport.at_top?
264
+ viewport.at_bottom?
265
+ ```
266
+
267
+ **In view:**
268
+
269
+ ```ruby
270
+ viewport.view
271
+ ```
272
+
273
+ **Programmatic scrolling:**
274
+
275
+ ```ruby
276
+ viewport.scroll_down(5)
277
+ viewport.scroll_up(5)
278
+ viewport.page_down
279
+ viewport.page_up
280
+ viewport.goto_top
281
+ viewport.goto_bottom
282
+ ```
283
+
284
+ ### List
285
+
286
+ **Interactive list with filtering:**
287
+
288
+ ```ruby
289
+ items = [
290
+ { title: "Item 1", description: "First item" },
291
+ { title: "Item 2", description: "Second item" }
292
+ ]
293
+
294
+ list = Bubbles::List.new(items, width: 40, height: 10)
295
+ list.title = "My List"
296
+ ```
297
+
298
+ **In update:**
299
+
300
+ ```ruby
301
+ list, command = list.update(message)
302
+ ```
112
303
 
113
- loop do
114
- command_queue.call_next
115
- sleep c.sleep_interval
304
+ **Get selection:**
305
+
306
+ ```ruby
307
+ list.selected_item
308
+ ```
309
+
310
+ **Filter state:**
311
+
312
+ ```ruby
313
+ list.filter_state
314
+ ```
315
+
316
+ **Styling:**
317
+
318
+ ```ruby
319
+ list.title_style = Lipgloss::Style.new.bold(true).foreground("212")
320
+ list.selected_item_style = Lipgloss::Style.new.foreground("212")
321
+ list.item_style = Lipgloss::Style.new.foreground("252")
322
+ ```
323
+
324
+ ### Table
325
+
326
+ **Data table with columns:**
327
+
328
+ ```ruby
329
+ columns = [
330
+ { title: "Name", width: 20 },
331
+ { title: "Age", width: 5 },
332
+ { title: "City", width: 15 }
333
+ ]
334
+
335
+ rows = [
336
+ ["Alice", "30", "New York"],
337
+ ["Bob", "25", "London"]
338
+ ]
339
+
340
+ table = Bubbles::Table.new(columns: columns, rows: rows, height: 10)
341
+ ```
342
+
343
+ **In update:**
344
+
345
+ ```ruby
346
+ table, command = table.update(message)
347
+ ```
348
+
349
+ **Get selection:**
350
+
351
+ ```ruby
352
+ table.selected_row
353
+ table.selected_row_data
354
+ ```
355
+
356
+ **Styling:**
357
+
358
+ ```ruby
359
+ table.header_style = Lipgloss::Style.new.bold(true).foreground("212")
360
+ table.cell_style = Lipgloss::Style.new.padding_left(1)
361
+ table.selected_style = Lipgloss::Style.new.bold(true).background("57")
362
+ ```
363
+
364
+ ### FilePicker
365
+
366
+ **File and directory browser:**
367
+
368
+ ```ruby
369
+ picker = Bubbles::FilePicker.new(directory: ".")
370
+ picker.height = 15
371
+ picker.show_hidden = false
372
+ picker.allowed_types = ["rb", "txt"]
373
+ ```
374
+
375
+ **In update:**
376
+
377
+ ```ruby
378
+ picker, command = picker.update(message)
379
+ ```
380
+
381
+ **Check for selection:**
382
+
383
+ ```ruby
384
+ if picker.did_select_file?
385
+ selected_path = picker.path
116
386
  end
117
387
  ```
118
388
 
119
- ..and execute:
389
+ **Options:**
390
+
391
+ ```ruby
392
+ picker.show_permissions = true
393
+ picker.show_size = true
394
+ picker.dir_allowed = false
395
+ picker.file_allowed = true
396
+ ```
397
+
398
+ ### Paginator
399
+
400
+ **Pagination controls:**
401
+
402
+ ```ruby
403
+ paginator = Bubbles::Paginator.new(type: Bubbles::Paginator::DOTS)
404
+ paginator.per_page = 10
405
+ paginator.update_total_pages(100)
406
+ ```
407
+
408
+ **Navigation:**
409
+
410
+ ```ruby
411
+ paginator.next_page
412
+ paginator.prev_page
413
+ ```
414
+
415
+ **Get slice bounds for your data:**
416
+
417
+ ```ruby
418
+ start_index, end_index = paginator.slice_bounds(items.length)
419
+ visible_items = items[start_index...end_index]
420
+ ```
421
+
422
+ **In view:**
423
+
424
+ ```ruby
425
+ paginator.view
426
+ ```
427
+
428
+ **Types:**
429
+
430
+ ```ruby
431
+ Bubbles::Paginator::ARABIC
432
+ Bubbles::Paginator::DOTS
433
+ ```
434
+
435
+ ### Help
436
+
437
+ **Help text generator:**
438
+
439
+ ```ruby
440
+ help = Bubbles::Help.new
441
+
442
+ bindings = [
443
+ Bubbles::Key.binding(keys: ["up", "k"], help: ["↑/k", "up"]),
444
+ Bubbles::Key.binding(keys: ["down", "j"], help: ["↓/j", "down"]),
445
+ Bubbles::Key.binding(keys: ["q"], help: ["q", "quit"])
446
+ ]
447
+
448
+ help.short_help_view(bindings)
449
+ ```
450
+
451
+ ### Key
452
+
453
+ **Key binding definitions:**
454
+
455
+ ```ruby
456
+ quit_binding = Bubbles::Key.binding(
457
+ keys: ["q", "ctrl+c"],
458
+ help: ["q", "quit"]
459
+ )
460
+ ```
461
+
462
+ **Check if a key matches:**
463
+
464
+ ```ruby
465
+ Bubbles::Key.matches?(message, quit_binding)
466
+ ```
120
467
 
121
- ```sh
122
- ruby my_bubbles_runner.rb
123
- # or
124
- bundle exec ruby my_bubbles_runner.rb
468
+ ### Cursor
469
+
470
+ **Blinking cursor for inputs:**
471
+
472
+ ```ruby
473
+ cursor = Bubbles::Cursor.new
474
+ cursor.char = "_"
475
+ cursor.focus
125
476
  ```
126
477
 
127
- > Look into [lib/bubbles/config.rb](https://github.com/equivalent/bubbles/blob/master/lib/bubbles/config.rb) for more details.
478
+ **Set cursor mode:**
479
+
480
+ ```ruby
481
+ cursor.set_mode(:blink)
482
+ cursor.set_mode(:static)
483
+ cursor.set_mode(:hide)
484
+ ```
485
+
486
+ **In update:**
487
+
488
+ ```ruby
489
+ cursor, command = cursor.update(message)
490
+ ```
491
+
492
+ **In view:**
493
+
494
+ ```ruby
495
+ cursor.view
496
+ ```
497
+
498
+ ## Complete Example
499
+
500
+ ```ruby
501
+ require "bubbletea"
502
+ require "lipgloss"
503
+ require "bubbles"
504
+
505
+ class MyApp
506
+ include Bubbletea::Model
507
+
508
+ def initialize
509
+ @spinner = Bubbles::Spinner.new
510
+ @spinner.spinner = Bubbles::Spinners::DOTS
511
+ end
512
+
513
+ def init
514
+ [self, @spinner.tick]
515
+ end
516
+
517
+ def update(message)
518
+ case message
519
+ when Bubbletea::KeyMessage
520
+ return [self, Bubbletea.quit] if message.to_s == "q"
521
+ end
522
+
523
+ @spinner, command = @spinner.update(message)
524
+ [self, command]
525
+ end
526
+
527
+ def view
528
+ "#{@spinner.view} Loading...\n\nPress q to quit"
529
+ end
530
+ end
531
+
532
+ Bubbletea.run(MyApp.new)
533
+ ```
128
534
 
129
535
  ## Development
130
536
 
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`.
537
+ **Requirements:**
538
+ - Ruby 3.2+
539
+ - [bubbletea-ruby](https://github.com/marcoroth/bubbletea-ruby)
540
+ - [lipgloss-ruby](https://github.com/marcoroth/lipgloss-ruby) (optional, for styling)
541
+
542
+ **Install dependencies:**
543
+
544
+ ```bash
545
+ bundle install
546
+ ```
547
+
548
+ **Run tests:**
549
+
550
+ ```bash
551
+ bundle exec rake test
552
+ ```
553
+
554
+ **Run demos:**
555
+
556
+ ```bash
557
+ ./demo/spinner
558
+ ./demo/progress
559
+ ./demo/textinput
560
+ ./demo/textarea
561
+ ./demo/viewport
562
+ ./demo/list
563
+ ./demo/table
564
+ ./demo/filepicker
565
+ ./demo/timer
566
+ ./demo/stopwatch
567
+ ./demo/paginator
568
+ ./demo/help
569
+ ./demo/cursor
570
+ ```
133
571
 
134
572
  ## Contributing
135
573
 
136
- Bug reports and pull requests are welcome on GitHub at https://github.com/equivalent/bubbles.
574
+ Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/bubbles-ruby.
575
+
576
+ ## License
577
+
578
+ The gem is available as open source under the terms of the MIT License.
137
579
 
580
+ ## Acknowledgments
138
581
 
582
+ This gem is a Ruby implementation of [charmbracelet/bubbles](https://github.com/charmbracelet/bubbles), part of the excellent [Charm](https://charm.sh) ecosystem.