fluxbit_view_components 0.3.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 (152) 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/component.rb +15 -8
  35. data/app/components/fluxbit/form/dropzone_component.rb +3 -3
  36. data/app/components/fluxbit/form/field_component.rb +4 -2
  37. data/app/components/fluxbit/form/help_text_component.rb +1 -1
  38. data/app/components/fluxbit/form/label_component.rb +10 -3
  39. data/app/components/fluxbit/form/password_component.rb +247 -0
  40. data/app/components/fluxbit/form/radio_group_button_component.rb +126 -0
  41. data/app/components/fluxbit/form/select_component.rb +108 -11
  42. data/app/components/fluxbit/form/text_field_component.rb +40 -23
  43. data/app/components/fluxbit/form/toggle_component.rb +2 -2
  44. data/app/components/fluxbit/form/upload_image_component.html.erb +3 -3
  45. data/app/components/fluxbit/form/upload_image_component.rb +12 -1
  46. data/app/components/fluxbit/gravatar_component.rb +7 -0
  47. data/app/components/fluxbit/icon_helpers.rb +167 -0
  48. data/app/components/fluxbit/link_component.rb +42 -0
  49. data/app/components/fluxbit/modal_component.rb +28 -31
  50. data/app/components/fluxbit/pagination_component.rb +206 -0
  51. data/app/components/fluxbit/popover_component.rb +14 -14
  52. data/app/components/fluxbit/progress_component.rb +196 -0
  53. data/app/components/fluxbit/skeleton_component.rb +237 -0
  54. data/app/components/fluxbit/speed_dial_action_component.html.erb +30 -0
  55. data/app/components/fluxbit/speed_dial_action_component.rb +59 -0
  56. data/app/components/fluxbit/speed_dial_component.html.erb +33 -0
  57. data/app/components/fluxbit/speed_dial_component.rb +73 -0
  58. data/app/components/fluxbit/spinner_component.rb +71 -0
  59. data/app/components/fluxbit/spinner_percent_component.rb +174 -0
  60. data/app/components/fluxbit/stepper_component.rb +223 -0
  61. data/app/components/fluxbit/tab_component.rb +44 -25
  62. data/app/components/fluxbit/table_component.rb +186 -0
  63. data/app/components/fluxbit/table_group_component.rb +28 -0
  64. data/app/components/fluxbit/theme_button_component.rb +64 -0
  65. data/app/components/fluxbit/timeline_component.rb +63 -0
  66. data/app/components/fluxbit/timeline_item_component.html.erb +64 -0
  67. data/app/components/fluxbit/timeline_item_component.rb +78 -0
  68. data/app/components/fluxbit/tooltip_component.rb +2 -2
  69. data/app/helpers/fluxbit/components_helper.rb +74 -4
  70. data/app/helpers/fluxbit/form_builder.rb +64 -15
  71. data/app/helpers/fluxbit/view_helper.rb +71 -0
  72. data/config/locales/en.yml +37 -4
  73. data/config/locales/pt-BR.yml +36 -0
  74. data/lib/fluxbit/config/accordion_component.rb +73 -0
  75. data/lib/fluxbit/config/avatar_component.rb +11 -11
  76. data/lib/fluxbit/config/badge_component.rb +14 -11
  77. data/lib/fluxbit/config/banner_component.rb +60 -0
  78. data/lib/fluxbit/config/bottom_navigation_component.rb +74 -0
  79. data/lib/fluxbit/config/breadcrumb_component.rb +24 -0
  80. data/lib/fluxbit/config/button_component.rb +6 -4
  81. data/lib/fluxbit/config/card_component.rb +23 -12
  82. data/lib/fluxbit/config/carousel_component.rb +33 -0
  83. data/lib/fluxbit/config/drawer_component.rb +48 -0
  84. data/lib/fluxbit/config/dropdown_component.rb +29 -0
  85. data/lib/fluxbit/config/form/check_box_component.rb +1 -1
  86. data/lib/fluxbit/config/form/dropzone_component.rb +1 -1
  87. data/lib/fluxbit/config/form/help_text_component.rb +1 -1
  88. data/lib/fluxbit/config/form/label_component.rb +3 -2
  89. data/lib/fluxbit/config/form/password_component.rb +19 -0
  90. data/lib/fluxbit/config/form/radio_group_button_component.rb +24 -0
  91. data/lib/fluxbit/config/form/text_field_component.rb +11 -11
  92. data/lib/fluxbit/config/form/toggle_component.rb +5 -5
  93. data/lib/fluxbit/config/link_component.rb +24 -0
  94. data/lib/fluxbit/config/modal_component.rb +1 -1
  95. data/lib/fluxbit/config/pagination_component.rb +31 -0
  96. data/lib/fluxbit/config/popover_component.rb +1 -1
  97. data/lib/fluxbit/config/progress_component.rb +63 -0
  98. data/lib/fluxbit/config/skeleton_component.rb +82 -0
  99. data/lib/fluxbit/config/speed_dial_component.rb +50 -0
  100. data/lib/fluxbit/config/spinner_component.rb +30 -0
  101. data/lib/fluxbit/config/spinner_percent_component.rb +61 -0
  102. data/lib/fluxbit/config/stepper_component.rb +299 -0
  103. data/lib/fluxbit/config/tab_component.rb +6 -0
  104. data/lib/fluxbit/config/table_component.rb +75 -0
  105. data/lib/fluxbit/config/theme_button_component.rb +19 -0
  106. data/lib/fluxbit/config/timeline_component.rb +77 -0
  107. data/lib/fluxbit/view_components/engine.rb +11 -3
  108. data/lib/fluxbit/view_components/version.rb +1 -1
  109. data/lib/fluxbit/view_components.rb +20 -0
  110. data/lib/generators/fluxbit/devise_views_generator.rb +116 -0
  111. data/lib/generators/fluxbit/pagy_generator.rb +39 -0
  112. data/lib/generators/fluxbit/scaffold_generator.rb +165 -0
  113. data/lib/generators/fluxbit/templates/_alert.html.erb.tt +1 -0
  114. data/lib/generators/fluxbit/templates/_flash.html.erb.tt +15 -0
  115. data/lib/generators/fluxbit/templates/_form.html.erb.tt +38 -0
  116. data/lib/generators/fluxbit/templates/_metadata.html.erb.tt +44 -0
  117. data/lib/generators/fluxbit/templates/controller.rb.tt +406 -0
  118. data/lib/generators/fluxbit/templates/create.turbo_stream.erb.tt +7 -0
  119. data/lib/generators/fluxbit/templates/destroy.turbo_stream.erb.tt +3 -0
  120. data/lib/generators/fluxbit/templates/destroy_all.turbo_stream.erb.tt +9 -0
  121. data/lib/generators/fluxbit/templates/devise_views/confirmations/new.html.erb +11 -0
  122. data/lib/generators/fluxbit/templates/devise_views/layouts/devise.html.erb +64 -0
  123. data/lib/generators/fluxbit/templates/devise_views/mailer/confirmation_instructions.html.erb +5 -0
  124. data/lib/generators/fluxbit/templates/devise_views/mailer/email_changed.html.erb +7 -0
  125. data/lib/generators/fluxbit/templates/devise_views/mailer/password_changed.html.erb +3 -0
  126. data/lib/generators/fluxbit/templates/devise_views/mailer/reset_password_instructions.html.erb +8 -0
  127. data/lib/generators/fluxbit/templates/devise_views/mailer/unlock_instructions.html.erb +7 -0
  128. data/lib/generators/fluxbit/templates/devise_views/passwords/edit.html.erb +29 -0
  129. data/lib/generators/fluxbit/templates/devise_views/passwords/new.html.erb +11 -0
  130. data/lib/generators/fluxbit/templates/devise_views/registrations/edit.html.erb +43 -0
  131. data/lib/generators/fluxbit/templates/devise_views/registrations/new.html.erb +34 -0
  132. data/lib/generators/fluxbit/templates/devise_views/sessions/new.html.erb +15 -0
  133. data/lib/generators/fluxbit/templates/devise_views/shared/_error_messages.html.erb +14 -0
  134. data/lib/generators/fluxbit/templates/devise_views/shared/_links.html.erb +25 -0
  135. data/lib/generators/fluxbit/templates/devise_views/unlocks/new.html.erb +11 -0
  136. data/lib/generators/fluxbit/templates/edit.html.erb.tt +47 -0
  137. data/lib/generators/fluxbit/templates/fluxbit_pagy.css +27 -0
  138. data/lib/generators/fluxbit/templates/i18n.en.yml.tt +121 -0
  139. data/lib/generators/fluxbit/templates/i18n.pt-BR.yml.tt +121 -0
  140. data/lib/generators/fluxbit/templates/index.html.erb.tt +254 -0
  141. data/lib/generators/fluxbit/templates/index.json.jbuilder.tt +33 -0
  142. data/lib/generators/fluxbit/templates/new.html.erb.tt +47 -0
  143. data/lib/generators/fluxbit/templates/partial.html.erb.tt +61 -0
  144. data/lib/generators/fluxbit/templates/policy.rb.tt +36 -0
  145. data/lib/generators/fluxbit/templates/send_alert_via_drawer.erb.tt +10 -0
  146. data/lib/generators/fluxbit/templates/show.html.erb.tt +44 -0
  147. data/lib/generators/fluxbit/templates/show.json.jbuilder.tt +6 -0
  148. data/lib/generators/fluxbit/templates/update.turbo_stream.erb.tt +10 -0
  149. data/lib/generators/fluxbit/templates/update_all.turbo_stream.erb.tt +20 -0
  150. data/lib/install/install.rb +58 -0
  151. metadata +107 -18
  152. 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
+ }