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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f5ca4fde5d32556cdbc0f25088912fa549531dea43e9e46557806c742a61a3c
4
- data.tar.gz: 2feca2ea7274688b6d1e850aee6dc6d5638718718584e977514f0abf96cedd8e
3
+ metadata.gz: 88d83b2e55472c883a4c14bb7df900c55c1ee9f69d128c4864b9edd4966cda26
4
+ data.tar.gz: 431db78fc6116407ee9244ef970110dcf24f5dd57ea540845543e7459ffbad5c
5
5
  SHA512:
6
- metadata.gz: 85ef792f0b9ead019bf89459531723043c7b771fe7b6c8f016ba03697d2e80d93139a081e0f4250d46545eb97b31f159fcaa9ba2bd5592c27053a0792c8063f5
7
- data.tar.gz: da59d2153f6ea5bb116ee44ae2dd7fd8cd8631c9024ebe7972d5103eb58f1fc05012953386d4520aed4247970dc00fc810ede2afc08a0ecc8329db053b3d5cb6
6
+ metadata.gz: 994ef29e0f99d930f9983558ce48b78eb67804bb43fb586c840acac7fc47049667f07cf93e6830f96d33b2d9a3d834a7bcee92923ce566aacdce45cac40d2c0b
7
+ data.tar.gz: f4bb70523e48dde4b7d4b97032ca09c793f81ef7bbda326b03e5bb387b21f379191f1de77b98d0e0d1fd8ffcfd4db09fd0743a52d6a1a5d67e3abcd6a3e3ed90
data/README.md CHANGED
@@ -24,6 +24,16 @@ Render Fluxbit ViewComponents:
24
24
  <%= fx_card(title: "Title") do %>
25
25
  <p>Card example</p>
26
26
  <% end %>
27
+
28
+ <!-- Breadcrumb navigation -->
29
+ <%= fx_breadcrumb do |c| %>
30
+ <% c.with_item href: "/" do %>Home<% end %>
31
+ <% c.with_item href: "/projects" do %>Projects<% end %>
32
+ <% c.with_item current_page: true do %>Current Page<% end %>
33
+ <% end %>
34
+
35
+ <!-- Pagination controls -->
36
+ <%= fx_pagination(page: 1, last: 10, count: 100) %>
27
37
  ```
28
38
 
29
39
  ## Dependencies
@@ -0,0 +1,49 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ assign(event) {
5
+ if (event.params["preventDefault"] === "true") {
6
+ event.preventDefault();
7
+ event.stopPropagation();
8
+ }
9
+
10
+ Object.keys(event.params["change"]).forEach(el => {
11
+ const targetElement = document.querySelector(el);
12
+ if (!targetElement) {
13
+ console.error(`fx-assigner: Target element "${el}" not found.`);
14
+ return;
15
+ }
16
+ Object.keys(event.params["change"][el]).forEach(attr => {
17
+ let value = "";
18
+ if (typeof event.params["change"][el][attr] == "object") {
19
+ const element = event.params["change"][el][attr]["element"];
20
+ const attribute = event.params["change"][el][attr]["attribute"];
21
+ const fromElement = document.querySelector(element);
22
+ if (!fromElement) {
23
+ console.error(`fx-assigner: Element "${element}" not found.`);
24
+ return;
25
+ }
26
+
27
+ if(attribute === "innerHTML")
28
+ value = fromElement.innerHTML;
29
+ else if (attribute === "value")
30
+ value = fromElement.value;
31
+ else if (attribute === "textContent")
32
+ value = fromElement.textContent;
33
+ else
34
+ value = fromElement.getAttribute(attribute);
35
+ } else
36
+ value = event.params["change"][el][attr];
37
+
38
+ if (attr === "innerHTML")
39
+ targetElement.innerHTML = value;
40
+ else if (attr === "value")
41
+ targetElement.value = value;
42
+ else if (attr === "textContent")
43
+ targetElement.textContent = value;
44
+ else
45
+ targetElement.setAttribute(attr, value);
46
+ });
47
+ });
48
+ }
49
+ }
@@ -0,0 +1,39 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ delay: { type: Number, default: 150 }
6
+ }
7
+
8
+ connect() {
9
+ this.timeout = null;
10
+ }
11
+
12
+ submit(event) {
13
+ // Clear any existing timeout
14
+ if (this.timeout) {
15
+ clearTimeout(this.timeout);
16
+ }
17
+
18
+ // Set new timeout
19
+ this.timeout = setTimeout(() => {
20
+ if (event.params["formId"]) {
21
+ const form = document.getElementById(event.params["formId"]);
22
+ if (!form) {
23
+ console.error(`fx-auto-submit: Form with ID "${event.params["formId"]}" not found.`);
24
+ return;
25
+ }
26
+ form.requestSubmit();
27
+ return;
28
+ }
29
+
30
+ this.element.requestSubmit();
31
+ }, this.delayValue);
32
+ }
33
+
34
+ disconnect() {
35
+ if (this.timeout) {
36
+ clearTimeout(this.timeout);
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,135 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = [ "drawer" ];
5
+ static values = {
6
+ autoShow: false,
7
+ placement: 'left',
8
+ backdrop: true,
9
+ bodyScrolling: false,
10
+ edge: false,
11
+ edgeOffset: String,
12
+ backdropClasses: 'bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30',
13
+ onHide: Object,
14
+ onShow: Object,
15
+ onToggle: Object
16
+ }
17
+
18
+ connect() {
19
+ this.drawers = {};
20
+
21
+ // 2 ways of using this controller:
22
+ if (this.drawerTargets.length === 0) {
23
+ // 1. If there is no drawerTarget, it will toggle the element itself
24
+ if (!this.element.id) this.element.id = 'drawer-' + Math.random().toString(36).substring(2, 15);
25
+
26
+ this._initDrawer(this.element, this._optionsFromElements());
27
+ this._addListeners(this.element);
28
+ if(this.autoShowValue) this.drawers[this.element.id].show();
29
+ } else {
30
+ // 2. Using the drawerTarget to initialize a single drawer
31
+ this.drawerTargets.forEach(target => {
32
+ if (!target.id) target.id = 'drawer-' + Math.random().toString(36).substring(2, 15);
33
+
34
+ this._initDrawer(target, this._optionsFromElements(target));
35
+ this._addListeners(target);
36
+ if (this.autoShowValue) this.drawers[target.id].show();
37
+ });
38
+ }
39
+ }
40
+
41
+ async _ensureDrawerLoaded() {
42
+ if (typeof Drawer === "undefined") {
43
+ const module = await import('flowbite');
44
+ window.Drawer = module.Drawer;
45
+ }
46
+ }
47
+
48
+ _initDrawer(target, options = {}) {
49
+ //await this._ensureDrawerLoaded();
50
+ this.drawers[target.id] = new Drawer(target, options);
51
+ }
52
+
53
+ _addListeners(target) {
54
+ if (!this._drawerListeners) this._drawerListeners = new Set();
55
+
56
+ ["show", "hide", "toggle"].forEach(action => {
57
+ const eventName = `${action}Drawer:${target.id}`;
58
+ if (!this._drawerListeners.has(eventName)) {
59
+ document.addEventListener(eventName, () => {
60
+ if (this.drawers[target.id]) this.drawers[target.id][action]();
61
+ });
62
+ this._drawerListeners.add(eventName);
63
+ }
64
+ });
65
+ }
66
+ disconnect() {
67
+ Object.entries(this.drawers).forEach(([id, drawer]) => {
68
+ if (drawer) {
69
+ drawer.hide();
70
+ drawer.destroy();
71
+ delete this.drawers[id];
72
+ }
73
+ });
74
+ }
75
+
76
+ _optionsFromElements(target = null) {
77
+ let options = {};
78
+ if (this.hasPlacementValue) options["placement"] = this.placementValue;
79
+ if (this.hasBackdropValue) options["backdrop"] = this.backdropValue;
80
+ if (this.hasBodyScrollingValue) options["bodyScrolling"] = this.bodyScrollingValue;
81
+ if (this.hasEdgeValue) options["edge"] = this.edgeValue;
82
+ if (this.hasEdgeOffsetValue) options["edgeOffset"] = this.edgeOffsetValue;
83
+ if (this.hasBackdropClassesValue) options["backdropClasses"] = this.backdropClassesValue;
84
+ if (this.hasOnHideValue) options["onHide"] = this.onHideValue;
85
+ if (this.hasOnShowValue) options["onShow"] = this.onShowValue;
86
+ if (this.hasOnToggleValue) options["onToggle"] = this.onToggleValue;
87
+
88
+ if (target) {
89
+ if (target.dataset.autoShow !== undefined) options["autoShow"] = target.dataset.autoShow === "true";
90
+ if (target.dataset.placement) options["placement"] = target.dataset.placement;
91
+ if (target.dataset.backdrop !== undefined) options["backdrop"] = target.dataset.backdrop === "true";
92
+ if (target.dataset.bodyScrolling !== undefined) options["bodyScrolling"] = target.dataset.bodyScrolling === "true";
93
+ if (target.dataset.edge !== undefined) options["edge"] = target.dataset.edge === "true";
94
+ if (target.dataset.edgeOffset) options["edgeOffset"] = target.dataset.edgeOffset;
95
+ if (target.dataset.backdropClasses) options["backdropClasses"] = target.dataset.backdropClasses;
96
+ if (target.dataset.onHide) options["onHide"] = target.dataset.onHide;
97
+ if (target.dataset.onShow) options["onShow"] = target.dataset.onShow;
98
+ if (target.dataset.onToggle) options["onToggle"] = target.dataset.onToggle;
99
+ }
100
+ return options;
101
+ }
102
+
103
+ _toCamelCase(str) {
104
+ return str.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
105
+ }
106
+
107
+ toggle(event) {
108
+ const targetId = event.target.dataset[this._toCamelCase(this.identifier + '-id')];
109
+ if (targetId) {
110
+ if (this.drawers[targetId]) this.drawers[targetId].toggle();
111
+ else console.warn(`Drawer with id ${targetId} not found.`);
112
+ } else
113
+ Object.entries(this.drawers).forEach(([_id, drawer]) => { if (drawer) drawer.toggle(); });
114
+ }
115
+
116
+ show(event) {
117
+ const targetId = event.target.dataset[this._toCamelCase(this.identifier + '-id')];
118
+ if (targetId) {
119
+ if (this.drawers[targetId]) this.drawers[targetId].show();
120
+ else console.warn(`Drawer with id ${targetId} not found.`);
121
+ } else
122
+ Object.entries(this.drawers).forEach(([_id, drawer]) => { if (drawer) drawer.show(); });
123
+ }
124
+
125
+ hide(event) {
126
+ const targetId = event.target.dataset[this._toCamelCase(this.identifier + '-id')];
127
+ if (targetId) {
128
+ if (this.drawers[targetId]) this.drawers[targetId].hide();
129
+ else console.warn(`Drawer with id ${targetId} not found.`);
130
+ } else
131
+ Object.entries(this.drawers).forEach(([_id, drawer]) => { if (drawer) drawer.hide(); });
132
+ }
133
+ }
134
+
135
+ // document.dispatchEvent(new CustomEvent("toggleDrawer:demo-drawer"));
@@ -0,0 +1,56 @@
1
+ import FxAssigner from './assigner_controller'
2
+ import FxAutoSubmit from './auto_submit_controller'
3
+ import FxDrawer from './drawer_controller'
4
+ import FxMethodLink from './method_link_controller'
5
+ import FxModal from './modal_controller'
6
+ import FxPassword from './password_controller'
7
+ import FxProgress from './progress_controller'
8
+ import FxRowClick from './row_click_controller'
9
+ import FxSelectAll from './select_all_controller'
10
+ import FxSpinnerPercent from './spinner_percent_controller'
11
+ import FxThemeButton from './theme_button_controller'
12
+
13
+ export {
14
+ FxAssigner,
15
+ FxAutoSubmit,
16
+ FxDrawer,
17
+ FxMethodLink,
18
+ FxModal,
19
+ FxPassword,
20
+ FxProgress,
21
+ FxRowClick,
22
+ FxSelectAll,
23
+ FxSpinnerPercent,
24
+ FxThemeButton
25
+ }
26
+
27
+ export function registerFluxbitControllers(application) {
28
+ application.register('fx-assigner', FxAssigner)
29
+ application.register('fx-auto-submit', FxAutoSubmit)
30
+ application.register('fx-drawer', FxDrawer)
31
+ application.register('fx-method-link', FxMethodLink)
32
+ application.register('fx-modal', FxModal)
33
+ application.register('fx-password', FxPassword)
34
+ application.register('fx-progress', FxProgress)
35
+ application.register('fx-row-click', FxRowClick)
36
+ application.register('fx-select-all', FxSelectAll)
37
+ application.register('fx-spinner-percent', FxSpinnerPercent)
38
+ application.register('fx-theme-button', FxThemeButton)
39
+
40
+ // Make controllers globally accessible for vanilla JS
41
+ if (typeof window !== 'undefined') {
42
+ window.FluxbitControllers = {
43
+ FxAssigner,
44
+ FxAutoSubmit,
45
+ FxDrawer,
46
+ FxMethodLink,
47
+ FxModal,
48
+ FxPassword,
49
+ FxProgress,
50
+ FxRowClick,
51
+ FxSelectAll,
52
+ FxSpinnerPercent,
53
+ FxThemeButton
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,143 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ method: "get",
6
+ url: String,
7
+ params: Object,
8
+ formDataId: String,
9
+ debug: false,
10
+ eventType: "click"
11
+ }
12
+
13
+ connect() {
14
+ this.element.addEventListener(this.eventTypeValue, this.click.bind(this));
15
+ }
16
+
17
+ disconnect() {
18
+ this.element.removeEventListener(this.eventTypeValue, this.click.bind(this));
19
+ }
20
+
21
+ click(event) {
22
+ event.preventDefault()
23
+ event.stopPropagation()
24
+
25
+ const formData = this.createFormData();
26
+
27
+ // Add any additional parameters
28
+ if (this.hasParamsValue) this.addParams(formData)
29
+
30
+ // Submit the request
31
+ const url = this.hasUrlValue ? this.urlValue : this.element.href
32
+ this.submitRequest(url, formData)
33
+ }
34
+
35
+ createFormData() {
36
+ const formData = new FormData()
37
+
38
+ if (this.hasFormDataIdValue) {
39
+ // Try to use existing form data
40
+ const existingForm = document.getElementById(this.formDataIdValue)
41
+ const formElements = existingForm.querySelectorAll("input, select, textarea")
42
+
43
+ formElements.forEach(element => {
44
+ if (element.name && (element.type !== "checkbox" || element.checked))
45
+ formData.append(element.name, element.value)
46
+ })
47
+ }
48
+
49
+ formData.append("_method", (this.methodValue || formData.method).toUpperCase())
50
+ formData.append("authenticity_token", this.getCSRFToken())
51
+ return formData
52
+ }
53
+
54
+ addParams(formData) {
55
+ this.appendNestedParams(formData, this.paramsValue, "")
56
+ }
57
+
58
+ appendNestedParams(formData, obj, prefix) {
59
+ Object.entries(obj).forEach(([key, value]) => {
60
+ const fullKey = prefix ? `${prefix}[${key}]` : key
61
+
62
+ if (typeof value === "object" && value !== null) {
63
+ // Check if it's an element reference object
64
+ if (this.isElementReference(value)) {
65
+ const resolvedValue = this.resolveElementValue(value)
66
+ formData.append(fullKey, resolvedValue)
67
+ } else {
68
+ // Recursively handle nested objects
69
+ this.appendNestedParams(formData, value, fullKey)
70
+ }
71
+ } else {
72
+ // Primitive value
73
+ formData.append(fullKey, value)
74
+ }
75
+ })
76
+ }
77
+
78
+ isElementReference(obj) {
79
+ return obj.hasOwnProperty("element") && obj.hasOwnProperty("attribute")
80
+ }
81
+
82
+ resolveElementValue(elementRef) {
83
+ const { element, attribute } = elementRef
84
+ const domElement = document.querySelector(element)
85
+
86
+ if (!domElement) {
87
+ console.error(`fx-method-link: Element "${element}" not found.`)
88
+ return ""
89
+ }
90
+
91
+ switch (attribute) {
92
+ case "innerHTML":
93
+ return domElement.innerHTML
94
+ case "value":
95
+ return domElement.value
96
+ case "textContent":
97
+ return domElement.textContent
98
+ default:
99
+ return domElement.getAttribute(attribute) || ""
100
+ }
101
+ }
102
+
103
+ submitRequest(url, formData) {
104
+ // Debug: Log form data contents
105
+ if (this.debugValue) {
106
+ console.log("Submitting form data:")
107
+ for (let [key, value] of formData.entries()) {
108
+ console.log(`${key}: ${value}`)
109
+ }
110
+ }
111
+
112
+ // Submit the request
113
+ fetch(url, {
114
+ method: "POST",
115
+ body: formData,
116
+ headers: {
117
+ "Accept": "text/vnd.turbo-stream.html, text/html",
118
+ "X-Requested-With": "XMLHttpRequest"
119
+ }
120
+ }).then(response => {
121
+ if (response.ok) {
122
+ // Handle successful response
123
+ if (response.headers.get("Content-Type")?.includes("turbo-stream")) {
124
+ return response.text().then(html => {
125
+ Turbo.renderStreamMessage(html)
126
+ })
127
+ } else {
128
+ // For regular HTML responses, you might want to redirect or reload
129
+ window.location.reload()
130
+ }
131
+ } else {
132
+ console.error("Request failed:", response.statusText)
133
+ }
134
+ }).catch(error => {
135
+ console.error("Network error:", error)
136
+ })
137
+ }
138
+
139
+ getCSRFToken() {
140
+ const token = document.querySelector('meta[name="csrf-token"]')
141
+ return token ? token.getAttribute("content") : ""
142
+ }
143
+ }
@@ -0,0 +1,118 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = [ "modal" ];
5
+ static values = {
6
+ autoShow: false,
7
+ placement: 'bottom-right',
8
+ backdrop: 'dynamic',
9
+ backdropClasses: 'bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30',
10
+ closable: false,
11
+ onHide: Object,
12
+ onShow: Object,
13
+ onToggle: Object
14
+ }
15
+
16
+ connect() {
17
+ this.modals = {};
18
+
19
+ // 2 ways of using this controller:
20
+ if (this.modalTargets.length === 0) {
21
+ // 1. If there is no modalTarget, it will toggle the element itself
22
+ if (!this.element.id) this.element.id = 'modal-' + Math.random().toString(36).substring(2, 15);
23
+
24
+ this._initModal(this.element, this._optionsFromElements());
25
+ this._addListeners(this.element);
26
+ if(this.autoShowValue) this.modals[this.element.id].show();
27
+ } else {
28
+ // 2. Using the modalTarget to initialize a single modal
29
+ this.modalTargets.forEach(target => {
30
+ if (!target.id) target.id = 'modal-' + Math.random().toString(36).substring(2, 15);
31
+ this._initModal(target, this._optionsFromElements(target));
32
+ this._addListeners(target);
33
+ if (this.autoShowValue) this.modals[target.id].show();
34
+ });
35
+ }
36
+ }
37
+
38
+ async _ensureModalLoaded() {
39
+ if (typeof Modal === "undefined") {
40
+ const module = await import('flowbite');
41
+ window.Modal = module.Modal;
42
+ }
43
+ }
44
+
45
+ async _initModal(target, options = {}) {
46
+ await this._ensureModalLoaded();
47
+ this.modals[target.id] = new Modal(target, options);
48
+ }
49
+
50
+ _addListeners(target) {
51
+ if (!this._modalListeners) this._modalListeners = new Set();
52
+
53
+ ["show", "hide", "toggle"].forEach(action => {
54
+ const eventName = `${action}Modal:${target.id}`;
55
+ if (!this._modalListeners.has(eventName)) {
56
+ document.addEventListener(eventName, () => {
57
+ this.modals[target.id][action]();
58
+ });
59
+ this._modalListeners.add(eventName);
60
+ }
61
+ });
62
+ }
63
+
64
+ _optionsFromElements(target = null) {
65
+ let options = {};
66
+ if (this.hasPlacementValue) options["placement"] = this.placementValue;
67
+ if (this.hasBackdropValue) options["backdrop"] = this.backdropValue;
68
+ if (this.hasBackdropClassesValue) options["backdropClasses"] = this.backdropClassesValue;
69
+ if (this.hasClosableValue) options["closable"] = this.closableValue;
70
+ if (this.hasOnHideValue) options["onHide"] = this.onHideValue;
71
+ if (this.hasOnShowValue) options["onShow"] = this.onShowValue;
72
+ if (this.hasOnToggleValue) options["onToggle"] = this.onToggleValue;
73
+
74
+ if (target) {
75
+ if (target.dataset.placement) options["placement"] = target.dataset.placement;
76
+ if (target.dataset.backdrop !== undefined) options["backdrop"] = target.dataset.backdrop === "true";
77
+ if (target.dataset.backdropClasses) options["backdropClasses"] = target.dataset.backdropClasses;
78
+ if (target.dataset.closable) options["closable"] = target.dataset.edgeOffset;
79
+ if (target.dataset.onHide) options["onHide"] = target.dataset.onHide;
80
+ if (target.dataset.onShow) options["onShow"] = target.dataset.onShow;
81
+ if (target.dataset.onToggle) options["onToggle"] = target.dataset.onToggle;
82
+ }
83
+ return options;
84
+ }
85
+
86
+ _toCamelCase(str) {
87
+ return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
88
+ }
89
+
90
+ toggle(event) {
91
+ const targetId = event.target.dataset[this._toCamelCase(this.identifier + '-id')];
92
+ if (targetId) {
93
+ if (this.modals[targetId]) this.modals[targetId].toggle();
94
+ else console.warn(`Modal with id ${targetId} not found.`);
95
+ } else
96
+ Object.entries(this.modals).forEach(([_id, modal]) => { if (modal) modal.toggle(); });
97
+ }
98
+
99
+ show(event) {
100
+ const targetId = event.target.dataset[this._toCamelCase(this.identifier + '-id')];
101
+ if (targetId) {
102
+ if (this.modals[targetId]) this.modals[targetId].show();
103
+ else console.warn(`Modal with id ${targetId} not found.`);
104
+ } else
105
+ Object.entries(this.modals).forEach(([_id, modal]) => { if (modal) modal.show(); });
106
+ }
107
+
108
+ hide(event) {
109
+ const targetId = event.target.dataset[this._toCamelCase(this.identifier + '-id')];
110
+ if (targetId) {
111
+ if (this.modals[targetId]) this.modals[targetId].hide();
112
+ else console.warn(`Modal with id ${targetId} not found.`);
113
+ } else
114
+ Object.entries(this.modals).forEach(([_id, modal]) => { if (modal) modal.hide(); });
115
+ }
116
+ }
117
+
118
+ // document.dispatchEvent(new CustomEvent("toggleModal:demo-modal"));