m9sh 0.2.1 → 0.2.2

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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/.idea/hotcdn.iml +30 -0
  3. data/.mise.toml +2 -2
  4. data/app/assets/config/manifest.js +4 -0
  5. data/app/assets/images/icons/activity.svg +3 -0
  6. data/app/assets/images/icons/bell.svg +4 -0
  7. data/app/assets/images/icons/book.svg +4 -0
  8. data/app/assets/images/icons/chevron-down.svg +3 -0
  9. data/app/assets/images/icons/chevron-left.svg +3 -0
  10. data/app/assets/images/icons/chevron-right.svg +3 -0
  11. data/app/assets/images/icons/credit-card.svg +4 -0
  12. data/app/assets/images/icons/dollar-sign.svg +3 -0
  13. data/app/assets/images/icons/edit.svg +4 -0
  14. data/app/assets/images/icons/github.svg +3 -0
  15. data/app/assets/images/icons/home.svg +4 -0
  16. data/app/assets/images/icons/info.svg +5 -0
  17. data/app/assets/images/icons/layout.svg +6 -0
  18. data/app/assets/images/icons/logout.svg +5 -0
  19. data/app/assets/images/icons/menu.svg +5 -0
  20. data/app/assets/images/icons/moon.svg +3 -0
  21. data/app/assets/images/icons/paintbrush.svg +6 -0
  22. data/app/assets/images/icons/search.svg +4 -0
  23. data/app/assets/images/icons/settings.svg +4 -0
  24. data/app/assets/images/icons/sun.svg +11 -0
  25. data/app/assets/images/icons/user.svg +4 -0
  26. data/app/assets/images/icons/users.svg +5 -0
  27. data/app/assets/stylesheets/tailwind.css +1180 -0
  28. data/app/components/backdrop_component.rb +103 -0
  29. data/app/components/docs/code_block_component.rb +56 -0
  30. data/app/components/docs/component_api_component.rb +16 -0
  31. data/app/components/docs/component_examples_component.rb +16 -0
  32. data/app/components/docs/component_header_component.html.erb +8 -0
  33. data/app/components/docs/component_header_component.rb +14 -0
  34. data/app/components/docs/component_installation_component.html.erb +15 -0
  35. data/app/components/docs/component_installation_component.rb +13 -0
  36. data/app/components/docs/component_page_component.html.erb +9 -0
  37. data/app/components/docs/component_page_component.rb +19 -0
  38. data/app/components/docs/component_preview_component.rb +318 -0
  39. data/app/components/docs/component_usage_component.rb +18 -0
  40. data/app/components/docs/prop_table_component.rb +64 -0
  41. data/app/controllers/application_controller.rb +3 -0
  42. data/app/controllers/blocks_controller.rb +51 -0
  43. data/app/controllers/docs_controller.rb +162 -0
  44. data/app/controllers/showcase_controller.rb +42 -0
  45. data/app/helpers/blocks_helper.rb +343 -0
  46. data/app/helpers/docs_helper.rb +3807 -0
  47. data/app/helpers/m9sh/toast_helper.rb +46 -0
  48. data/app/helpers/m9sh_helper.rb +343 -0
  49. data/app/javascript/application.js +3 -0
  50. data/app/javascript/controllers/application.js +9 -0
  51. data/app/javascript/controllers/backdrop_controller.js +137 -0
  52. data/app/javascript/controllers/color_customizer_controller.js +569 -0
  53. data/app/javascript/controllers/color_theme_controller.js +120 -0
  54. data/app/javascript/controllers/docs/component_preview_controller.js +149 -0
  55. data/app/javascript/controllers/docs/copy_button_controller.js +20 -0
  56. data/app/javascript/controllers/index.js +6 -0
  57. data/app/javascript/controllers/theme_controller.js +23 -0
  58. data/app/views/blocks/_sidebar.html.erb +31 -0
  59. data/app/views/blocks/_toc.html.erb +29 -0
  60. data/app/views/blocks/examples/dashboard-01.html.erb +180 -0
  61. data/app/views/blocks/examples/dashboard-02.html.erb +190 -0
  62. data/app/views/blocks/examples/dashboard-03.html.erb +210 -0
  63. data/app/views/blocks/examples/settings-01.html.erb +220 -0
  64. data/app/views/blocks/examples/settings-02.html.erb +231 -0
  65. data/app/views/blocks/examples/settings-03.html.erb +340 -0
  66. data/app/views/blocks/index.html.erb +65 -0
  67. data/app/views/docs/_sidebar.html.erb +47 -0
  68. data/app/views/docs/_toc.html.erb +19 -0
  69. data/app/views/docs/about.html.erb +68 -0
  70. data/app/views/docs/components/accordion.html.erb +196 -0
  71. data/app/views/docs/components/alert.html.erb +272 -0
  72. data/app/views/docs/components/alert_dialog.html.erb +232 -0
  73. data/app/views/docs/components/avatar.html.erb +207 -0
  74. data/app/views/docs/components/badge.html.erb +145 -0
  75. data/app/views/docs/components/breadcrumb.html.erb +264 -0
  76. data/app/views/docs/components/button.html.erb +229 -0
  77. data/app/views/docs/components/card.html.erb +378 -0
  78. data/app/views/docs/components/checkbox.html.erb +212 -0
  79. data/app/views/docs/components/collapsible.html.erb +252 -0
  80. data/app/views/docs/components/dialog.html.erb +323 -0
  81. data/app/views/docs/components/dropdown_menu.html.erb +289 -0
  82. data/app/views/docs/components/hover_card.html.erb +220 -0
  83. data/app/views/docs/components/input.html.erb +254 -0
  84. data/app/views/docs/components/label.html.erb +128 -0
  85. data/app/views/docs/components/main.html.erb +352 -0
  86. data/app/views/docs/components/navbar.html.erb +394 -0
  87. data/app/views/docs/components/navigation_menu.html.erb +226 -0
  88. data/app/views/docs/components/popover.html.erb +267 -0
  89. data/app/views/docs/components/progress.html.erb +107 -0
  90. data/app/views/docs/components/radio_group.html.erb +209 -0
  91. data/app/views/docs/components/select.html.erb +260 -0
  92. data/app/views/docs/components/separator.html.erb +162 -0
  93. data/app/views/docs/components/sheet.html.erb +270 -0
  94. data/app/views/docs/components/sidebar.html.erb +597 -0
  95. data/app/views/docs/components/skeleton.html.erb +150 -0
  96. data/app/views/docs/components/slider.html.erb +218 -0
  97. data/app/views/docs/components/spinner.html.erb +132 -0
  98. data/app/views/docs/components/switch.html.erb +148 -0
  99. data/app/views/docs/components/table.html.erb +259 -0
  100. data/app/views/docs/components/tabs.html.erb +225 -0
  101. data/app/views/docs/components/textarea.html.erb +239 -0
  102. data/app/views/docs/components/theme_toggle.html.erb +135 -0
  103. data/app/views/docs/components/toast.html.erb +205 -0
  104. data/app/views/docs/components/toaster.html.erb +227 -0
  105. data/app/views/docs/components/toggle.html.erb +154 -0
  106. data/app/views/docs/components/tooltip.html.erb +216 -0
  107. data/app/views/docs/components/typography.html.erb +180 -0
  108. data/app/views/docs/index.html.erb +143 -0
  109. data/app/views/docs/installation.html.erb +155 -0
  110. data/app/views/docs/simple_test.html.erb +13 -0
  111. data/app/views/docs/test_accordion.html.erb +14 -0
  112. data/app/views/docs/usage.html.erb +272 -0
  113. data/app/views/layouts/application.html.erb +107 -0
  114. data/app/views/layouts/backdrop.html.erb +77 -0
  115. data/app/views/shared/_app_navbar.html.erb +240 -0
  116. data/app/views/shared/_navbar.html.erb +69 -0
  117. data/app/views/showcase/v2/_components_grid.html.erb +38 -0
  118. data/app/views/showcase/v2/_features.html.erb +59 -0
  119. data/app/views/showcase/v2/_forms.html.erb +195 -0
  120. data/app/views/showcase/v2/_hero.html.erb +55 -0
  121. data/app/views/showcase/v2/_metrics.html.erb +107 -0
  122. data/app/views/showcase/v2.html.erb +18 -0
  123. data/lib/m9sh/version.rb +1 -1
  124. data/m9sh.gemspec +1 -1
  125. metadata +120 -1
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module M9sh
4
+ module ToastHelper
5
+ def toast(title, description: nil, variant: :default, duration: 5000)
6
+ turbo_stream.append "toast-container" do
7
+ render M9sh::ToastComponent.new(
8
+ title: title,
9
+ description: description,
10
+ variant: variant,
11
+ duration: duration
12
+ )
13
+ end
14
+ end
15
+
16
+ def toast_success(title, description: nil, duration: 5000)
17
+ toast(title, description: description, variant: :success, duration: duration)
18
+ end
19
+
20
+ def toast_error(title, description: nil, duration: 5000)
21
+ toast(title, description: description, variant: :error, duration: duration)
22
+ end
23
+
24
+ def toast_warning(title, description: nil, duration: 5000)
25
+ toast(title, description: description, variant: :warning, duration: duration)
26
+ end
27
+
28
+ def toast_info(title, description: nil, duration: 5000)
29
+ toast(title, description: description, variant: :info, duration: duration)
30
+ end
31
+
32
+ # JavaScript helper for triggering toasts from Stimulus
33
+ def toast_js(title, description: nil, variant: :default, duration: 5000)
34
+ %{
35
+ window.dispatchEvent(new CustomEvent('m9sh:toast', {
36
+ detail: {
37
+ title: '#{j(title)}',
38
+ description: #{description ? "'#{j(description)}'" : 'null'},
39
+ variant: '#{variant}',
40
+ duration: #{duration}
41
+ }
42
+ }))
43
+ }.html_safe
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,343 @@
1
+ # frozen_string_literal: true
2
+
3
+ # M9sh UI Component Helpers
4
+ #
5
+ # Provides shorter, more ergonomic syntax for rendering m9sh/ui components
6
+ # inspired by shadcn/ui's JSX syntax.
7
+ #
8
+ # Usage Examples:
9
+ # # Short syntax
10
+ # <%= ui_button(variant: :outline, class: "w-full") { "Click me" } %>
11
+ #
12
+ # # Backwards compatible (alias)
13
+ # <%= render_button(variant: :outline) { "Click me" } %>
14
+ #
15
+ # # Original syntax still works
16
+ # <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
17
+ # Click me
18
+ # <% end %>
19
+ #
20
+ module M9shHelper
21
+ # Button component
22
+ # @param variant [Symbol] Button variant (:default, :secondary, :destructive, :outline, :ghost, :link)
23
+ # @param size [Symbol] Button size (:sm, :md, :lg, :icon)
24
+ # @param type [String] HTML button type
25
+ # @param disabled [Boolean] Whether button is disabled
26
+ # @param attrs [Hash] Additional HTML attributes
27
+ def ui_button(variant: :default, size: :md, type: "button", disabled: false, **attrs, &block)
28
+ render M9sh::ButtonComponent.new(
29
+ variant: variant,
30
+ size: size,
31
+ type: type,
32
+ disabled: disabled,
33
+ **attrs
34
+ ), &block
35
+ end
36
+ alias_method :render_button, :ui_button
37
+
38
+ # Badge component
39
+ # @param variant [Symbol] Badge variant (:default, :secondary, :destructive, :outline)
40
+ # @param attrs [Hash] Additional HTML attributes
41
+ def ui_badge(variant: :default, **attrs, &block)
42
+ render M9sh::BadgeComponent.new(variant: variant, **attrs), &block
43
+ end
44
+ alias_method :render_badge, :ui_badge
45
+
46
+ # Card component
47
+ # @param attrs [Hash] Additional HTML attributes
48
+ def ui_card(**attrs, &block)
49
+ render M9sh::CardComponent.new(**attrs), &block
50
+ end
51
+ alias_method :render_card, :ui_card
52
+
53
+ # Input component
54
+ # @param type [String] Input type
55
+ # @param placeholder [String] Placeholder text
56
+ # @param disabled [Boolean] Whether input is disabled
57
+ # @param attrs [Hash] Additional HTML attributes
58
+ def ui_input(type: "text", placeholder: nil, disabled: false, **attrs)
59
+ render M9sh::InputComponent.new(
60
+ type: type,
61
+ placeholder: placeholder,
62
+ disabled: disabled,
63
+ **attrs
64
+ )
65
+ end
66
+ alias_method :render_input, :ui_input
67
+
68
+ # Label component
69
+ # @param for_id [String] HTML for attribute
70
+ # @param attrs [Hash] Additional HTML attributes
71
+ def ui_label(for_id: nil, **attrs, &block)
72
+ render M9sh::LabelComponent.new(for: for_id, **attrs), &block
73
+ end
74
+ alias_method :render_label, :ui_label
75
+
76
+ # Textarea component
77
+ # @param placeholder [String] Placeholder text
78
+ # @param disabled [Boolean] Whether textarea is disabled
79
+ # @param rows [Integer] Number of rows
80
+ # @param attrs [Hash] Additional HTML attributes
81
+ def ui_textarea(placeholder: nil, disabled: false, rows: 3, **attrs, &block)
82
+ render M9sh::TextareaComponent.new(
83
+ placeholder: placeholder,
84
+ disabled: disabled,
85
+ rows: rows,
86
+ **attrs
87
+ ), &block
88
+ end
89
+ alias_method :render_textarea, :ui_textarea
90
+
91
+ # Checkbox component
92
+ # @param checked [Boolean] Whether checkbox is checked
93
+ # @param disabled [Boolean] Whether checkbox is disabled
94
+ # @param attrs [Hash] Additional HTML attributes
95
+ def ui_checkbox(checked: false, disabled: false, **attrs)
96
+ render M9sh::CheckboxComponent.new(
97
+ checked: checked,
98
+ disabled: disabled,
99
+ **attrs
100
+ )
101
+ end
102
+ alias_method :render_checkbox, :ui_checkbox
103
+
104
+ # Switch component
105
+ # @param checked [Boolean] Whether switch is checked
106
+ # @param disabled [Boolean] Whether switch is disabled
107
+ # @param attrs [Hash] Additional HTML attributes
108
+ def ui_switch(checked: false, disabled: false, **attrs)
109
+ render M9sh::SwitchComponent.new(
110
+ checked: checked,
111
+ disabled: disabled,
112
+ **attrs
113
+ )
114
+ end
115
+ alias_method :render_switch, :ui_switch
116
+
117
+ # Separator component
118
+ # @param orientation [Symbol] Separator orientation (:horizontal, :vertical)
119
+ # @param attrs [Hash] Additional HTML attributes
120
+ def ui_separator(orientation: :horizontal, **attrs)
121
+ render M9sh::SeparatorComponent.new(orientation: orientation, **attrs)
122
+ end
123
+ alias_method :render_separator, :ui_separator
124
+
125
+ # Spinner component
126
+ # @param size [Symbol] Spinner size (:sm, :md, :lg)
127
+ # @param attrs [Hash] Additional HTML attributes
128
+ def ui_spinner(size: :md, **attrs)
129
+ render M9sh::SpinnerComponent.new(size: size, **attrs)
130
+ end
131
+ alias_method :render_spinner, :ui_spinner
132
+
133
+ # Skeleton component
134
+ # @param attrs [Hash] Additional HTML attributes
135
+ def ui_skeleton(**attrs)
136
+ render M9sh::SkeletonComponent.new(**attrs)
137
+ end
138
+ alias_method :render_skeleton, :ui_skeleton
139
+
140
+ # Progress component
141
+ # @param value [Integer] Progress value (0-100)
142
+ # @param attrs [Hash] Additional HTML attributes
143
+ def ui_progress(value: 0, **attrs)
144
+ render M9sh::ProgressComponent.new(value: value, **attrs)
145
+ end
146
+ alias_method :render_progress, :ui_progress
147
+
148
+ # Avatar component
149
+ # @param src [String] Image source
150
+ # @param alt [String] Alt text
151
+ # @param fallback [String] Fallback text
152
+ # @param attrs [Hash] Additional HTML attributes
153
+ def ui_avatar(src: nil, alt: nil, fallback: nil, **attrs, &block)
154
+ render M9sh::AvatarComponent.new(
155
+ src: src,
156
+ alt: alt,
157
+ fallback: fallback,
158
+ **attrs
159
+ ), &block
160
+ end
161
+ alias_method :render_avatar, :ui_avatar
162
+
163
+ # Alert component
164
+ # @param variant [Symbol] Alert variant (:default, :destructive)
165
+ # @param attrs [Hash] Additional HTML attributes
166
+ def ui_alert(variant: :default, **attrs, &block)
167
+ render M9sh::AlertComponent.new(variant: variant, **attrs), &block
168
+ end
169
+ alias_method :render_alert, :ui_alert
170
+
171
+ # Slider component
172
+ # @param min [Integer] Minimum value
173
+ # @param max [Integer] Maximum value
174
+ # @param step [Integer] Step value
175
+ # @param value [Integer] Current value
176
+ # @param attrs [Hash] Additional HTML attributes
177
+ def ui_slider(min: 0, max: 100, step: 1, value: 0, **attrs)
178
+ render M9sh::SliderComponent.new(
179
+ min: min,
180
+ max: max,
181
+ step: step,
182
+ value: value,
183
+ **attrs
184
+ )
185
+ end
186
+ alias_method :render_slider, :ui_slider
187
+
188
+ # Toggle component
189
+ # @param pressed [Boolean] Whether toggle is pressed
190
+ # @param disabled [Boolean] Whether toggle is disabled
191
+ # @param attrs [Hash] Additional HTML attributes
192
+ def ui_toggle(pressed: false, disabled: false, **attrs, &block)
193
+ render M9sh::ToggleComponent.new(
194
+ pressed: pressed,
195
+ disabled: disabled,
196
+ **attrs
197
+ ), &block
198
+ end
199
+ alias_method :render_toggle, :ui_toggle
200
+
201
+ # Accordion component (complex, may need builder pattern)
202
+ # @param type [String] Accordion type ("single", "multiple")
203
+ # @param collapsible [Boolean] Whether accordion is collapsible
204
+ # @param default_value [String] Default open item value
205
+ # @param attrs [Hash] Additional HTML attributes
206
+ def ui_accordion(type: "single", collapsible: true, default_value: nil, **attrs, &block)
207
+ render M9sh::AccordionComponent.new(
208
+ type: type,
209
+ collapsible: collapsible,
210
+ default_value: default_value,
211
+ **attrs
212
+ ), &block
213
+ end
214
+ alias_method :render_accordion, :ui_accordion
215
+
216
+ # Tabs component (complex, may need builder pattern)
217
+ # @param default_value [String] Default active tab
218
+ # @param attrs [Hash] Additional HTML attributes
219
+ def ui_tabs(default_value: nil, **attrs, &block)
220
+ render M9sh::TabsComponent.new(default_value: default_value, **attrs), &block
221
+ end
222
+ alias_method :render_tabs, :ui_tabs
223
+
224
+ # Dialog component (complex, may need builder pattern)
225
+ # @param open [Boolean] Whether dialog is open
226
+ # @param attrs [Hash] Additional HTML attributes
227
+ def ui_dialog(open: false, **attrs, &block)
228
+ render M9sh::DialogComponent.new(open: open, **attrs), &block
229
+ end
230
+ alias_method :render_dialog, :ui_dialog
231
+
232
+ # Alert Dialog component (complex, may need builder pattern)
233
+ # @param open [Boolean] Whether alert dialog is open
234
+ # @param attrs [Hash] Additional HTML attributes
235
+ def ui_alert_dialog(open: false, **attrs, &block)
236
+ render M9sh::AlertDialogComponent.new(open: open, **attrs), &block
237
+ end
238
+ alias_method :render_alert_dialog, :ui_alert_dialog
239
+
240
+ # Sheet component (complex, may need builder pattern)
241
+ # @param side [Symbol] Sheet side (:top, :right, :bottom, :left)
242
+ # @param open [Boolean] Whether sheet is open
243
+ # @param attrs [Hash] Additional HTML attributes
244
+ def ui_sheet(side: :right, open: false, **attrs, &block)
245
+ render M9sh::SheetComponent.new(side: side, open: open, **attrs), &block
246
+ end
247
+ alias_method :render_sheet, :ui_sheet
248
+
249
+ # Popover component (complex, may need builder pattern)
250
+ # @param open [Boolean] Whether popover is open
251
+ # @param attrs [Hash] Additional HTML attributes
252
+ def ui_popover(open: false, **attrs, &block)
253
+ render M9sh::PopoverComponent.new(open: open, **attrs), &block
254
+ end
255
+ alias_method :render_popover, :ui_popover
256
+
257
+ # Tooltip component (complex, may need builder pattern)
258
+ # @param attrs [Hash] Additional HTML attributes
259
+ def ui_tooltip(**attrs, &block)
260
+ render M9sh::TooltipComponent.new(**attrs), &block
261
+ end
262
+ alias_method :render_tooltip, :ui_tooltip
263
+
264
+ # Hover Card component (complex, may need builder pattern)
265
+ # @param attrs [Hash] Additional HTML attributes
266
+ def ui_hover_card(**attrs, &block)
267
+ render M9sh::HoverCardComponent.new(**attrs), &block
268
+ end
269
+ alias_method :render_hover_card, :ui_hover_card
270
+
271
+ # Dropdown Menu component (complex, may need builder pattern)
272
+ # @param attrs [Hash] Additional HTML attributes
273
+ def ui_dropdown_menu(**attrs, &block)
274
+ render M9sh::DropdownMenuComponent.new(**attrs), &block
275
+ end
276
+ alias_method :render_dropdown_menu, :ui_dropdown_menu
277
+
278
+ # Collapsible component
279
+ # @param open [Boolean] Whether collapsible is open
280
+ # @param attrs [Hash] Additional HTML attributes
281
+ def ui_collapsible(open: false, **attrs, &block)
282
+ render M9sh::CollapsibleComponent.new(open: open, **attrs), &block
283
+ end
284
+ alias_method :render_collapsible, :ui_collapsible
285
+
286
+ # Table component
287
+ # @param attrs [Hash] Additional HTML attributes
288
+ def ui_table(**attrs, &block)
289
+ render M9sh::TableComponent.new(**attrs), &block
290
+ end
291
+ alias_method :render_table, :ui_table
292
+
293
+ # Breadcrumb component
294
+ # @param attrs [Hash] Additional HTML attributes
295
+ def ui_breadcrumb(**attrs, &block)
296
+ render M9sh::BreadcrumbComponent.new(**attrs), &block
297
+ end
298
+ alias_method :render_breadcrumb, :ui_breadcrumb
299
+
300
+ # Navigation Menu component
301
+ # @param attrs [Hash] Additional HTML attributes
302
+ def ui_navigation_menu(**attrs, &block)
303
+ render M9sh::NavigationMenuComponent.new(**attrs), &block
304
+ end
305
+ alias_method :render_navigation_menu, :ui_navigation_menu
306
+
307
+ # Radio Group component
308
+ # @param name [String] Input name
309
+ # @param value [String] Selected value
310
+ # @param attrs [Hash] Additional HTML attributes
311
+ def ui_radio_group(name: nil, value: nil, **attrs, &block)
312
+ render M9sh::RadioGroupComponent.new(name: name, value: value, **attrs), &block
313
+ end
314
+ alias_method :render_radio_group, :ui_radio_group
315
+
316
+ # Select component
317
+ # @param placeholder [String] Placeholder text
318
+ # @param disabled [Boolean] Whether select is disabled
319
+ # @param attrs [Hash] Additional HTML attributes
320
+ def ui_select(placeholder: nil, disabled: false, **attrs, &block)
321
+ render M9sh::SelectComponent.new(
322
+ placeholder: placeholder,
323
+ disabled: disabled,
324
+ **attrs
325
+ ), &block
326
+ end
327
+ alias_method :render_select, :ui_select
328
+
329
+ # Typography component
330
+ # @param variant [Symbol] Typography variant
331
+ # @param attrs [Hash] Additional HTML attributes
332
+ def ui_typography(variant: :p, **attrs, &block)
333
+ render M9sh::TypographyComponent.new(variant: variant, **attrs), &block
334
+ end
335
+ alias_method :render_typography, :ui_typography
336
+
337
+ # Theme Toggle component
338
+ # @param attrs [Hash] Additional HTML attributes
339
+ def ui_theme_toggle(**attrs)
340
+ render M9sh::ThemeToggleComponent.new(**attrs)
341
+ end
342
+ alias_method :render_theme_toggle, :ui_theme_toggle
343
+ end
@@ -0,0 +1,3 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails"
3
+ import "controllers"
@@ -0,0 +1,9 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }
@@ -0,0 +1,137 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["leftSidebar", "rightSidebar", "overlay", "leftToggle", "rightToggle"]
5
+ static values = {
6
+ leftOpen: { type: Boolean, default: false },
7
+ rightOpen: { type: Boolean, default: false }
8
+ }
9
+
10
+ connect() {
11
+ // Check if mobile
12
+ this.checkMobile()
13
+
14
+ // Apply initial state to ensure proper desktop display
15
+ this.applyState()
16
+
17
+ // Listen for resize events
18
+ window.addEventListener("resize", this.checkMobile.bind(this))
19
+ }
20
+
21
+ disconnect() {
22
+ window.removeEventListener("resize", this.checkMobile.bind(this))
23
+ }
24
+
25
+ checkMobile() {
26
+ this.isMobile = window.innerWidth < 768
27
+
28
+ // If switching to desktop, close all mobile sidebars
29
+ if (!this.isMobile) {
30
+ this.leftOpenValue = false
31
+ this.rightOpenValue = false
32
+ this.applyState()
33
+ }
34
+ }
35
+
36
+ toggleLeft() {
37
+ if (!this.isMobile) return
38
+
39
+ this.leftOpenValue = !this.leftOpenValue
40
+
41
+ // Close right sidebar if opening left
42
+ if (this.leftOpenValue) {
43
+ this.rightOpenValue = false
44
+ }
45
+
46
+ this.applyState()
47
+ }
48
+
49
+ toggleRight() {
50
+ if (!this.isMobile) return
51
+
52
+ this.rightOpenValue = !this.rightOpenValue
53
+
54
+ // Close left sidebar if opening right
55
+ if (this.rightOpenValue) {
56
+ this.leftOpenValue = false
57
+ }
58
+
59
+ this.applyState()
60
+ }
61
+
62
+ closeAll() {
63
+ this.leftOpenValue = false
64
+ this.rightOpenValue = false
65
+ this.applyState()
66
+ }
67
+
68
+ applyState() {
69
+ const anyOpen = this.leftOpenValue || this.rightOpenValue
70
+
71
+ // Update overlay
72
+ if (this.hasOverlayTarget) {
73
+ if (anyOpen && this.isMobile) {
74
+ this.overlayTarget.classList.remove("hidden")
75
+ } else {
76
+ this.overlayTarget.classList.add("hidden")
77
+ }
78
+ }
79
+
80
+ // Update left sidebar (only on mobile, desktop handled by CSS)
81
+ if (this.hasLeftSidebarTarget && this.isMobile) {
82
+ if (this.leftOpenValue) {
83
+ // Slide into view from left edge
84
+ this.leftSidebarTarget.classList.remove("-translate-x-full")
85
+ this.leftSidebarTarget.classList.add("translate-x-0")
86
+ } else {
87
+ // Slide out to left edge
88
+ this.leftSidebarTarget.classList.remove("translate-x-0")
89
+ this.leftSidebarTarget.classList.add("-translate-x-full")
90
+ }
91
+ }
92
+
93
+ // Update right sidebar (only on mobile, desktop handled by CSS)
94
+ if (this.hasRightSidebarTarget && this.isMobile) {
95
+ if (this.rightOpenValue) {
96
+ // Slide into view from right edge
97
+ this.rightSidebarTarget.classList.remove("translate-x-full")
98
+ this.rightSidebarTarget.classList.add("translate-x-0")
99
+ } else {
100
+ // Slide out to right edge
101
+ this.rightSidebarTarget.classList.remove("translate-x-0")
102
+ this.rightSidebarTarget.classList.add("translate-x-full")
103
+ }
104
+ }
105
+
106
+ // Update toggle button icons rotation (chevrons)
107
+ if (this.hasLeftToggleTarget) {
108
+ const icon = this.leftToggleTarget.querySelector("svg")
109
+ if (icon) {
110
+ if (this.leftOpenValue) {
111
+ icon.style.transform = "rotate(180deg)"
112
+ } else {
113
+ icon.style.transform = "rotate(0deg)"
114
+ }
115
+ }
116
+ }
117
+
118
+ if (this.hasRightToggleTarget) {
119
+ const icon = this.rightToggleTarget.querySelector("svg")
120
+ if (icon) {
121
+ if (this.rightOpenValue) {
122
+ icon.style.transform = "rotate(180deg)"
123
+ } else {
124
+ icon.style.transform = "rotate(0deg)"
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ leftOpenValueChanged() {
131
+ this.applyState()
132
+ }
133
+
134
+ rightOpenValueChanged() {
135
+ this.applyState()
136
+ }
137
+ }