fluxbit_view_components 0.2.0 → 0.4.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -0
  3. data/app/assets/javascripts/fluxbit_view_components/assigner_controller.js +49 -0
  4. data/app/assets/javascripts/fluxbit_view_components/auto_submit_controller.js +39 -0
  5. data/app/assets/javascripts/fluxbit_view_components/drawer_controller.js +135 -0
  6. data/app/assets/javascripts/fluxbit_view_components/index.js +56 -0
  7. data/app/assets/javascripts/fluxbit_view_components/method_link_controller.js +143 -0
  8. data/app/assets/javascripts/fluxbit_view_components/modal_controller.js +118 -0
  9. data/app/assets/javascripts/fluxbit_view_components/password_controller.js +170 -0
  10. data/app/assets/javascripts/fluxbit_view_components/progress_controller.js +374 -0
  11. data/app/assets/javascripts/fluxbit_view_components/row_click_controller.js +32 -0
  12. data/app/assets/javascripts/fluxbit_view_components/select_all_controller.js +122 -0
  13. data/app/assets/javascripts/fluxbit_view_components/spinner_percent_controller.js +174 -0
  14. data/app/assets/javascripts/fluxbit_view_components/theme_button_controller.js +90 -0
  15. data/app/assets/javascripts/fluxbit_view_components.js +1175 -0
  16. data/app/components/fluxbit/accordion_component.rb +125 -0
  17. data/app/components/fluxbit/alert_component.rb +8 -8
  18. data/app/components/fluxbit/avatar_component.rb +11 -12
  19. data/app/components/fluxbit/avatar_group_component.rb +1 -1
  20. data/app/components/fluxbit/badge_component.rb +8 -7
  21. data/app/components/fluxbit/banner_component.rb +139 -0
  22. data/app/components/fluxbit/bottom_navigation_component.rb +437 -0
  23. data/app/components/fluxbit/breadcrumb_component.rb +66 -0
  24. data/app/components/fluxbit/button_component.rb +39 -11
  25. data/app/components/fluxbit/button_group_component.rb +1 -1
  26. data/app/components/fluxbit/card_component.rb +26 -23
  27. data/app/components/fluxbit/carousel_component.rb +154 -0
  28. data/app/components/fluxbit/component.rb +24 -3
  29. data/app/components/fluxbit/drawer_component.html.erb +30 -0
  30. data/app/components/fluxbit/drawer_component.rb +125 -0
  31. data/app/components/fluxbit/dropdown_component.rb +41 -0
  32. data/app/components/fluxbit/dropdown_item_component.rb +68 -0
  33. data/app/components/fluxbit/flex_component.rb +1 -1
  34. data/app/components/fluxbit/form/check_box_component.rb +56 -0
  35. data/app/components/fluxbit/form/component.rb +27 -26
  36. data/app/components/fluxbit/form/dropzone_component.html.erb +39 -0
  37. data/app/components/fluxbit/form/dropzone_component.rb +39 -0
  38. data/app/components/fluxbit/form/field_component.rb +28 -0
  39. data/app/components/fluxbit/form/form_builder_component.rb +1 -1
  40. data/app/components/fluxbit/form/{helper_text_component.rb → help_text_component.rb} +9 -4
  41. data/app/components/fluxbit/form/label_component.rb +40 -30
  42. data/app/components/fluxbit/form/password_component.rb +247 -0
  43. data/app/components/fluxbit/form/radio_group_button_component.rb +126 -0
  44. data/app/components/fluxbit/form/range_component.rb +52 -0
  45. data/app/components/fluxbit/form/select_component.rb +185 -0
  46. data/app/components/fluxbit/form/text_field_component.rb +185 -0
  47. data/app/components/fluxbit/form/toggle_component.html.erb +23 -0
  48. data/app/components/fluxbit/form/toggle_component.rb +81 -0
  49. data/app/components/fluxbit/form/upload_image_component.html.erb +50 -0
  50. data/app/components/fluxbit/form/upload_image_component.rb +61 -0
  51. data/app/components/fluxbit/gravatar_component.rb +7 -0
  52. data/app/components/fluxbit/icon_helpers.rb +167 -0
  53. data/app/components/fluxbit/link_component.rb +42 -0
  54. data/app/components/fluxbit/modal_component.rb +28 -31
  55. data/app/components/fluxbit/pagination_component.rb +206 -0
  56. data/app/components/fluxbit/popover_component.rb +14 -14
  57. data/app/components/fluxbit/progress_component.rb +196 -0
  58. data/app/components/fluxbit/skeleton_component.rb +237 -0
  59. data/app/components/fluxbit/speed_dial_action_component.html.erb +30 -0
  60. data/app/components/fluxbit/speed_dial_action_component.rb +59 -0
  61. data/app/components/fluxbit/speed_dial_component.html.erb +33 -0
  62. data/app/components/fluxbit/speed_dial_component.rb +73 -0
  63. data/app/components/fluxbit/spinner_component.rb +71 -0
  64. data/app/components/fluxbit/spinner_percent_component.rb +174 -0
  65. data/app/components/fluxbit/stepper_component.rb +223 -0
  66. data/app/components/fluxbit/tab_component.rb +44 -25
  67. data/app/components/fluxbit/table_component.rb +186 -0
  68. data/app/components/fluxbit/table_group_component.rb +28 -0
  69. data/app/components/fluxbit/theme_button_component.rb +64 -0
  70. data/app/components/fluxbit/timeline_component.rb +63 -0
  71. data/app/components/fluxbit/timeline_item_component.html.erb +64 -0
  72. data/app/components/fluxbit/timeline_item_component.rb +78 -0
  73. data/app/components/fluxbit/tooltip_component.rb +2 -2
  74. data/app/helpers/fluxbit/components_helper.rb +93 -51
  75. data/app/helpers/fluxbit/form_builder.rb +136 -0
  76. data/app/helpers/fluxbit/view_helper.rb +71 -0
  77. data/config/locales/en.yml +37 -4
  78. data/config/locales/pt-BR.yml +36 -0
  79. data/lib/fluxbit/config/accordion_component.rb +73 -0
  80. data/lib/fluxbit/config/avatar_component.rb +11 -11
  81. data/lib/fluxbit/config/badge_component.rb +14 -11
  82. data/lib/fluxbit/config/banner_component.rb +60 -0
  83. data/lib/fluxbit/config/bottom_navigation_component.rb +74 -0
  84. data/lib/fluxbit/config/breadcrumb_component.rb +24 -0
  85. data/lib/fluxbit/config/button_component.rb +6 -4
  86. data/lib/fluxbit/config/card_component.rb +23 -12
  87. data/lib/fluxbit/config/carousel_component.rb +33 -0
  88. data/lib/fluxbit/config/drawer_component.rb +48 -0
  89. data/lib/fluxbit/config/dropdown_component.rb +29 -0
  90. data/lib/fluxbit/config/form/check_box_component.rb +19 -0
  91. data/lib/fluxbit/config/form/dropzone_component.rb +20 -0
  92. data/lib/fluxbit/config/form/{helper_text_component.rb → help_text_component.rb} +2 -2
  93. data/lib/fluxbit/config/form/label_component.rb +31 -0
  94. data/lib/fluxbit/config/form/password_component.rb +19 -0
  95. data/lib/fluxbit/config/form/radio_group_button_component.rb +24 -0
  96. data/lib/fluxbit/config/form/range_component.rb +15 -0
  97. data/lib/fluxbit/config/form/text_field_component.rb +76 -0
  98. data/lib/fluxbit/config/form/toggle_component.rb +79 -0
  99. data/lib/fluxbit/config/link_component.rb +24 -0
  100. data/lib/fluxbit/config/modal_component.rb +1 -1
  101. data/lib/fluxbit/config/pagination_component.rb +31 -0
  102. data/lib/fluxbit/config/popover_component.rb +1 -1
  103. data/lib/fluxbit/config/progress_component.rb +63 -0
  104. data/lib/fluxbit/config/skeleton_component.rb +82 -0
  105. data/lib/fluxbit/config/speed_dial_component.rb +50 -0
  106. data/lib/fluxbit/config/spinner_component.rb +30 -0
  107. data/lib/fluxbit/config/spinner_percent_component.rb +61 -0
  108. data/lib/fluxbit/config/stepper_component.rb +299 -0
  109. data/lib/fluxbit/config/tab_component.rb +6 -0
  110. data/lib/fluxbit/config/table_component.rb +75 -0
  111. data/lib/fluxbit/config/theme_button_component.rb +19 -0
  112. data/lib/fluxbit/config/timeline_component.rb +77 -0
  113. data/lib/fluxbit/view_components/engine.rb +11 -3
  114. data/lib/fluxbit/view_components/version.rb +1 -1
  115. data/lib/fluxbit/view_components.rb +27 -1
  116. data/lib/generators/fluxbit/devise_views_generator.rb +116 -0
  117. data/lib/generators/fluxbit/pagy_generator.rb +39 -0
  118. data/lib/generators/fluxbit/scaffold_generator.rb +165 -0
  119. data/lib/generators/fluxbit/templates/_alert.html.erb.tt +1 -0
  120. data/lib/generators/fluxbit/templates/_flash.html.erb.tt +15 -0
  121. data/lib/generators/fluxbit/templates/_form.html.erb.tt +38 -0
  122. data/lib/generators/fluxbit/templates/_metadata.html.erb.tt +44 -0
  123. data/lib/generators/fluxbit/templates/controller.rb.tt +406 -0
  124. data/lib/generators/fluxbit/templates/create.turbo_stream.erb.tt +7 -0
  125. data/lib/generators/fluxbit/templates/destroy.turbo_stream.erb.tt +3 -0
  126. data/lib/generators/fluxbit/templates/destroy_all.turbo_stream.erb.tt +9 -0
  127. data/lib/generators/fluxbit/templates/devise_views/confirmations/new.html.erb +11 -0
  128. data/lib/generators/fluxbit/templates/devise_views/layouts/devise.html.erb +64 -0
  129. data/lib/generators/fluxbit/templates/devise_views/mailer/confirmation_instructions.html.erb +5 -0
  130. data/lib/generators/fluxbit/templates/devise_views/mailer/email_changed.html.erb +7 -0
  131. data/lib/generators/fluxbit/templates/devise_views/mailer/password_changed.html.erb +3 -0
  132. data/lib/generators/fluxbit/templates/devise_views/mailer/reset_password_instructions.html.erb +8 -0
  133. data/lib/generators/fluxbit/templates/devise_views/mailer/unlock_instructions.html.erb +7 -0
  134. data/lib/generators/fluxbit/templates/devise_views/passwords/edit.html.erb +29 -0
  135. data/lib/generators/fluxbit/templates/devise_views/passwords/new.html.erb +11 -0
  136. data/lib/generators/fluxbit/templates/devise_views/registrations/edit.html.erb +43 -0
  137. data/lib/generators/fluxbit/templates/devise_views/registrations/new.html.erb +34 -0
  138. data/lib/generators/fluxbit/templates/devise_views/sessions/new.html.erb +15 -0
  139. data/lib/generators/fluxbit/templates/devise_views/shared/_error_messages.html.erb +14 -0
  140. data/lib/generators/fluxbit/templates/devise_views/shared/_links.html.erb +25 -0
  141. data/lib/generators/fluxbit/templates/devise_views/unlocks/new.html.erb +11 -0
  142. data/lib/generators/fluxbit/templates/edit.html.erb.tt +47 -0
  143. data/lib/generators/fluxbit/templates/fluxbit_pagy.css +27 -0
  144. data/lib/generators/fluxbit/templates/i18n.en.yml.tt +121 -0
  145. data/lib/generators/fluxbit/templates/i18n.pt-BR.yml.tt +121 -0
  146. data/lib/generators/fluxbit/templates/index.html.erb.tt +254 -0
  147. data/lib/generators/fluxbit/templates/index.json.jbuilder.tt +33 -0
  148. data/lib/generators/fluxbit/templates/new.html.erb.tt +47 -0
  149. data/lib/generators/fluxbit/templates/partial.html.erb.tt +61 -0
  150. data/lib/generators/fluxbit/templates/policy.rb.tt +36 -0
  151. data/lib/generators/fluxbit/templates/send_alert_via_drawer.erb.tt +10 -0
  152. data/lib/generators/fluxbit/templates/show.html.erb.tt +44 -0
  153. data/lib/generators/fluxbit/templates/show.json.jbuilder.tt +6 -0
  154. data/lib/generators/fluxbit/templates/update.turbo_stream.erb.tt +10 -0
  155. data/lib/generators/fluxbit/templates/update_all.turbo_stream.erb.tt +20 -0
  156. data/lib/install/install.rb +61 -3
  157. metadata +127 -35
  158. data/LICENSE.txt +0 -20
  159. data/app/components/fluxbit/form/checkbox_input_component.rb +0 -61
  160. data/app/components/fluxbit/form/datepicker_component.rb +0 -7
  161. data/app/components/fluxbit/form/radio_input_component.rb +0 -21
  162. data/app/components/fluxbit/form/range_input_component.rb +0 -51
  163. data/app/components/fluxbit/form/select_free_input_component.rb +0 -77
  164. data/app/components/fluxbit/form/select_input_component.rb +0 -21
  165. data/app/components/fluxbit/form/spacer_input_component.rb +0 -12
  166. data/app/components/fluxbit/form/text_input_component.rb +0 -225
  167. data/app/components/fluxbit/form/textarea_input_component.rb +0 -57
  168. data/app/components/fluxbit/form/toggle_input_component.rb +0 -166
  169. data/app/components/fluxbit/form/upload_image_input_component.html.erb +0 -48
  170. data/app/components/fluxbit/form/upload_image_input_component.rb +0 -61
  171. data/app/components/fluxbit/form/upload_input_component.html.erb +0 -12
  172. data/app/components/fluxbit/form/upload_input_component.rb +0 -47
  173. data/app/helpers/fluxbit/classes_helper.rb +0 -9
@@ -0,0 +1,174 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["progress", "text"];
5
+ static values = {
6
+ percent: {
7
+ type: Number,
8
+ default: 0
9
+ },
10
+ animate: {
11
+ type: Boolean,
12
+ default: false
13
+ },
14
+ speed: {
15
+ type: String,
16
+ default: "normal"
17
+ },
18
+ hasCustomText: {
19
+ type: Boolean,
20
+ default: false
21
+ }
22
+ };
23
+
24
+ connect() {
25
+ this.circumference = 2 * Math.PI * 45; // radius of 45
26
+ this.updateProgress();
27
+ this.updateAnimation();
28
+ }
29
+
30
+ percentValueChanged() {
31
+ this.updateProgress();
32
+ }
33
+
34
+ animateValueChanged() {
35
+ this.updateAnimation();
36
+ }
37
+
38
+ speedValueChanged() {
39
+ this.updateAnimation();
40
+ }
41
+
42
+ updateProgress() {
43
+ const clampedPercent = Math.max(0, Math.min(100, this.percentValue));
44
+ const offset = this.circumference - (clampedPercent / 100) * this.circumference;
45
+
46
+ if (this.hasProgressTarget) {
47
+ this.progressTarget.style.strokeDashoffset = offset;
48
+ this.progressTarget.setAttribute('aria-valuenow', clampedPercent);
49
+ }
50
+
51
+ if (this.hasTextTarget && !this.hasCustomTextValue) {
52
+ this.textTarget.textContent = `${clampedPercent}%`;
53
+ }
54
+
55
+ // Update the component's aria attributes
56
+ this.element.setAttribute('aria-valuenow', clampedPercent);
57
+ }
58
+
59
+ updateAnimation() {
60
+ const svg = this.element.querySelector('svg');
61
+ if (!svg) return;
62
+
63
+ if (this.animateValue) {
64
+ // Set animation duration directly via CSS custom property
65
+ let duration;
66
+ switch (this.speedValue) {
67
+ case 'slow':
68
+ duration = '3s';
69
+ break;
70
+ case 'fast':
71
+ duration = '0.5s';
72
+ break;
73
+ case 'very_fast':
74
+ duration = '0.3s';
75
+ break;
76
+ case 'normal':
77
+ default:
78
+ duration = '1s';
79
+ break;
80
+ }
81
+
82
+ // Apply the animation with custom duration
83
+ svg.style.animation = `spin ${duration} linear infinite`;
84
+ } else {
85
+ // Remove animation
86
+ svg.style.animation = '';
87
+ }
88
+ }
89
+
90
+ // Public method to update percentage programmatically
91
+ setPercent(percent) {
92
+ this.percentValue = percent;
93
+ }
94
+
95
+ // Public method to toggle animation
96
+ setAnimate(animate) {
97
+ this.animateValue = animate;
98
+ }
99
+
100
+ // Public method to start animation
101
+ startAnimation() {
102
+ this.animateValue = true;
103
+ }
104
+
105
+ // Public method to stop animation
106
+ stopAnimation() {
107
+ this.animateValue = false;
108
+ }
109
+
110
+ // Public method to set animation speed
111
+ setSpeed(speed) {
112
+ this.speedValue = speed;
113
+ }
114
+
115
+ // Public method to animate to a new percentage
116
+ animateToPercent(targetPercent, duration = 1000) {
117
+ const startPercent = this.percentValue;
118
+ const difference = targetPercent - startPercent;
119
+ const startTime = performance.now();
120
+
121
+ // Cancel any existing animation
122
+ if (this.animationId) {
123
+ cancelAnimationFrame(this.animationId);
124
+ }
125
+
126
+ const animate = (currentTime) => {
127
+ const elapsed = currentTime - startTime;
128
+ const progress = Math.min(elapsed / duration, 1);
129
+
130
+ // Easing function (ease-out)
131
+ const easeOut = 1 - Math.pow(1 - progress, 3);
132
+ const currentPercent = startPercent + (difference * easeOut);
133
+
134
+ // Update directly without triggering Stimulus value change
135
+ this.updateProgressDirect(Math.round(currentPercent));
136
+
137
+ if (progress < 1) {
138
+ this.animationId = requestAnimationFrame(animate);
139
+ } else {
140
+ // Set final value through Stimulus to maintain sync
141
+ this.percentValue = targetPercent;
142
+ this.animationId = null;
143
+ }
144
+ };
145
+
146
+ this.animationId = requestAnimationFrame(animate);
147
+ }
148
+
149
+ // Direct update method that doesn't trigger Stimulus value change
150
+ updateProgressDirect(percent) {
151
+ const clampedPercent = Math.max(0, Math.min(100, percent));
152
+ const offset = this.circumference - (clampedPercent / 100) * this.circumference;
153
+
154
+ if (this.hasProgressTarget) {
155
+ this.progressTarget.style.strokeDashoffset = offset;
156
+ this.progressTarget.setAttribute('aria-valuenow', clampedPercent);
157
+ }
158
+
159
+ if (this.hasTextTarget && !this.hasCustomTextValue) {
160
+ this.textTarget.textContent = `${clampedPercent}%`;
161
+ }
162
+
163
+ // Update the component's aria attributes
164
+ this.element.setAttribute('aria-valuenow', clampedPercent);
165
+ }
166
+
167
+ disconnect() {
168
+ // Clean up animation when controller is disconnected
169
+ if (this.animationId) {
170
+ cancelAnimationFrame(this.animationId);
171
+ this.animationId = null;
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,90 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["lightIcon", "darkIcon", "systemIcon"]
5
+ static values = {
6
+ theme: { type: String, default: "system" }
7
+ }
8
+
9
+ connect() {
10
+ // Load saved theme from localStorage or use default
11
+ this.themeValue = this.getSavedTheme() || "system"
12
+ this.applyTheme(this.themeValue)
13
+ this.updateIcon()
14
+ }
15
+
16
+ toggle() {
17
+ // Cycle through: light -> dark -> system -> light
18
+ const themes = ["light", "dark", "system"]
19
+ const currentIndex = themes.indexOf(this.themeValue)
20
+ const nextIndex = (currentIndex + 1) % themes.length
21
+ this.themeValue = themes[nextIndex]
22
+
23
+ this.applyTheme(this.themeValue)
24
+ this.saveTheme(this.themeValue)
25
+ this.updateIcon()
26
+
27
+ // Dispatch custom event for other components to listen to
28
+ this.dispatch("changed", { detail: { theme: this.themeValue } })
29
+ }
30
+
31
+ applyTheme(theme) {
32
+ const html = document.documentElement
33
+
34
+ if (theme === "system") {
35
+ // Remove explicit theme, use system preference
36
+ localStorage.removeItem("theme")
37
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
38
+ html.classList.add("dark")
39
+ } else {
40
+ html.classList.remove("dark")
41
+ }
42
+ } else if (theme === "dark") {
43
+ localStorage.setItem("theme", "dark")
44
+ html.classList.add("dark")
45
+ } else {
46
+ localStorage.setItem("theme", "light")
47
+ html.classList.remove("dark")
48
+ }
49
+ }
50
+
51
+ getSavedTheme() {
52
+ const saved = localStorage.getItem("theme")
53
+ if (saved) return saved
54
+
55
+ // If no saved theme, check if dark class is present
56
+ if (document.documentElement.classList.contains("dark")) {
57
+ return "dark"
58
+ }
59
+
60
+ return "system"
61
+ }
62
+
63
+ saveTheme(theme) {
64
+ if (theme === "system") {
65
+ localStorage.removeItem("theme")
66
+ } else {
67
+ localStorage.setItem("theme", theme)
68
+ }
69
+ }
70
+
71
+ updateIcon() {
72
+ // Hide all icons first
73
+ this.lightIconTargets.forEach(icon => icon.classList.add("hidden"))
74
+ this.darkIconTargets.forEach(icon => icon.classList.add("hidden"))
75
+ this.systemIconTargets.forEach(icon => icon.classList.add("hidden"))
76
+
77
+ // Show the current theme icon
78
+ if (this.themeValue === "light" && this.hasLightIconTarget) {
79
+ this.lightIconTargets.forEach(icon => icon.classList.remove("hidden"))
80
+ } else if (this.themeValue === "dark" && this.hasDarkIconTarget) {
81
+ this.darkIconTargets.forEach(icon => icon.classList.remove("hidden"))
82
+ } else if (this.themeValue === "system" && this.hasSystemIconTarget) {
83
+ this.systemIconTargets.forEach(icon => icon.classList.remove("hidden"))
84
+ }
85
+ }
86
+
87
+ themeValueChanged() {
88
+ this.updateIcon()
89
+ }
90
+ }