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.
- checksums.yaml +4 -4
- data/.idea/hotcdn.iml +30 -0
- data/.mise.toml +2 -2
- data/app/assets/config/manifest.js +4 -0
- data/app/assets/images/icons/activity.svg +3 -0
- data/app/assets/images/icons/bell.svg +4 -0
- data/app/assets/images/icons/book.svg +4 -0
- data/app/assets/images/icons/chevron-down.svg +3 -0
- data/app/assets/images/icons/chevron-left.svg +3 -0
- data/app/assets/images/icons/chevron-right.svg +3 -0
- data/app/assets/images/icons/credit-card.svg +4 -0
- data/app/assets/images/icons/dollar-sign.svg +3 -0
- data/app/assets/images/icons/edit.svg +4 -0
- data/app/assets/images/icons/github.svg +3 -0
- data/app/assets/images/icons/home.svg +4 -0
- data/app/assets/images/icons/info.svg +5 -0
- data/app/assets/images/icons/layout.svg +6 -0
- data/app/assets/images/icons/logout.svg +5 -0
- data/app/assets/images/icons/menu.svg +5 -0
- data/app/assets/images/icons/moon.svg +3 -0
- data/app/assets/images/icons/paintbrush.svg +6 -0
- data/app/assets/images/icons/search.svg +4 -0
- data/app/assets/images/icons/settings.svg +4 -0
- data/app/assets/images/icons/sun.svg +11 -0
- data/app/assets/images/icons/user.svg +4 -0
- data/app/assets/images/icons/users.svg +5 -0
- data/app/assets/stylesheets/tailwind.css +1180 -0
- data/app/components/backdrop_component.rb +103 -0
- data/app/components/docs/code_block_component.rb +56 -0
- data/app/components/docs/component_api_component.rb +16 -0
- data/app/components/docs/component_examples_component.rb +16 -0
- data/app/components/docs/component_header_component.html.erb +8 -0
- data/app/components/docs/component_header_component.rb +14 -0
- data/app/components/docs/component_installation_component.html.erb +15 -0
- data/app/components/docs/component_installation_component.rb +13 -0
- data/app/components/docs/component_page_component.html.erb +9 -0
- data/app/components/docs/component_page_component.rb +19 -0
- data/app/components/docs/component_preview_component.rb +318 -0
- data/app/components/docs/component_usage_component.rb +18 -0
- data/app/components/docs/prop_table_component.rb +64 -0
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/blocks_controller.rb +51 -0
- data/app/controllers/docs_controller.rb +162 -0
- data/app/controllers/showcase_controller.rb +42 -0
- data/app/helpers/blocks_helper.rb +343 -0
- data/app/helpers/docs_helper.rb +3807 -0
- data/app/helpers/m9sh/toast_helper.rb +46 -0
- data/app/helpers/m9sh_helper.rb +343 -0
- data/app/javascript/application.js +3 -0
- data/app/javascript/controllers/application.js +9 -0
- data/app/javascript/controllers/backdrop_controller.js +137 -0
- data/app/javascript/controllers/color_customizer_controller.js +569 -0
- data/app/javascript/controllers/color_theme_controller.js +120 -0
- data/app/javascript/controllers/docs/component_preview_controller.js +149 -0
- data/app/javascript/controllers/docs/copy_button_controller.js +20 -0
- data/app/javascript/controllers/index.js +6 -0
- data/app/javascript/controllers/theme_controller.js +23 -0
- data/app/views/blocks/_sidebar.html.erb +31 -0
- data/app/views/blocks/_toc.html.erb +29 -0
- data/app/views/blocks/examples/dashboard-01.html.erb +180 -0
- data/app/views/blocks/examples/dashboard-02.html.erb +190 -0
- data/app/views/blocks/examples/dashboard-03.html.erb +210 -0
- data/app/views/blocks/examples/settings-01.html.erb +220 -0
- data/app/views/blocks/examples/settings-02.html.erb +231 -0
- data/app/views/blocks/examples/settings-03.html.erb +340 -0
- data/app/views/blocks/index.html.erb +65 -0
- data/app/views/docs/_sidebar.html.erb +47 -0
- data/app/views/docs/_toc.html.erb +19 -0
- data/app/views/docs/about.html.erb +68 -0
- data/app/views/docs/components/accordion.html.erb +196 -0
- data/app/views/docs/components/alert.html.erb +272 -0
- data/app/views/docs/components/alert_dialog.html.erb +232 -0
- data/app/views/docs/components/avatar.html.erb +207 -0
- data/app/views/docs/components/badge.html.erb +145 -0
- data/app/views/docs/components/breadcrumb.html.erb +264 -0
- data/app/views/docs/components/button.html.erb +229 -0
- data/app/views/docs/components/card.html.erb +378 -0
- data/app/views/docs/components/checkbox.html.erb +212 -0
- data/app/views/docs/components/collapsible.html.erb +252 -0
- data/app/views/docs/components/dialog.html.erb +323 -0
- data/app/views/docs/components/dropdown_menu.html.erb +289 -0
- data/app/views/docs/components/hover_card.html.erb +220 -0
- data/app/views/docs/components/input.html.erb +254 -0
- data/app/views/docs/components/label.html.erb +128 -0
- data/app/views/docs/components/main.html.erb +352 -0
- data/app/views/docs/components/navbar.html.erb +394 -0
- data/app/views/docs/components/navigation_menu.html.erb +226 -0
- data/app/views/docs/components/popover.html.erb +267 -0
- data/app/views/docs/components/progress.html.erb +107 -0
- data/app/views/docs/components/radio_group.html.erb +209 -0
- data/app/views/docs/components/select.html.erb +260 -0
- data/app/views/docs/components/separator.html.erb +162 -0
- data/app/views/docs/components/sheet.html.erb +270 -0
- data/app/views/docs/components/sidebar.html.erb +597 -0
- data/app/views/docs/components/skeleton.html.erb +150 -0
- data/app/views/docs/components/slider.html.erb +218 -0
- data/app/views/docs/components/spinner.html.erb +132 -0
- data/app/views/docs/components/switch.html.erb +148 -0
- data/app/views/docs/components/table.html.erb +259 -0
- data/app/views/docs/components/tabs.html.erb +225 -0
- data/app/views/docs/components/textarea.html.erb +239 -0
- data/app/views/docs/components/theme_toggle.html.erb +135 -0
- data/app/views/docs/components/toast.html.erb +205 -0
- data/app/views/docs/components/toaster.html.erb +227 -0
- data/app/views/docs/components/toggle.html.erb +154 -0
- data/app/views/docs/components/tooltip.html.erb +216 -0
- data/app/views/docs/components/typography.html.erb +180 -0
- data/app/views/docs/index.html.erb +143 -0
- data/app/views/docs/installation.html.erb +155 -0
- data/app/views/docs/simple_test.html.erb +13 -0
- data/app/views/docs/test_accordion.html.erb +14 -0
- data/app/views/docs/usage.html.erb +272 -0
- data/app/views/layouts/application.html.erb +107 -0
- data/app/views/layouts/backdrop.html.erb +77 -0
- data/app/views/shared/_app_navbar.html.erb +240 -0
- data/app/views/shared/_navbar.html.erb +69 -0
- data/app/views/showcase/v2/_components_grid.html.erb +38 -0
- data/app/views/showcase/v2/_features.html.erb +59 -0
- data/app/views/showcase/v2/_forms.html.erb +195 -0
- data/app/views/showcase/v2/_hero.html.erb +55 -0
- data/app/views/showcase/v2/_metrics.html.erb +107 -0
- data/app/views/showcase/v2.html.erb +18 -0
- data/lib/m9sh/version.rb +1 -1
- data/m9sh.gemspec +1 -1
- 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,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
|
+
}
|