m9sh 0.2.2 → 0.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58b74eb111bd5e0edbc12f4448c6ddfde0ad6b6cacdeadbe604c9ca0fa1226d3
4
- data.tar.gz: f21c715a96ce2517b50f3da13bde38fe550226f97b10d0f2d169a0b705a5f885
3
+ metadata.gz: 60a17049e08e32e54ff30a314c07e746712f7d0cf10c84daef8794c44ac53520
4
+ data.tar.gz: 8331db49172a68f2605c2b4cdc6d4feadfafc97a0199143a974f007646ccb69a
5
5
  SHA512:
6
- metadata.gz: c30dfae60aa915c7acb4a9b38b4f665491bb9e9d368e52ffbc9050c57e175f529dad3efd900d85b4193ea56818d248c15ee233a8c154e8fefd1b426c564d4d05
7
- data.tar.gz: 54121f63d5f536988a55ac5033e6f5632f619e1be2380ec5922402010879fa2f5ca1e0349160bc1719fee176c07cd5982b7f6487f8c18cc3efad64ae1846d36c
6
+ metadata.gz: b64d1a98d913d375cdc090d050177164c88037a75286723754a25f1f4b668c2f68873c6730b5baec9e76d3b83769c4e82f5b85a899b6607f57bcb5ad706c7a54
7
+ data.tar.gz: e47067baeeed55078b0340573d333ec5d677d45cee791a6b33679a97b095f6194504f12f99647ccfcecf2b8254e22c08ddc2780e016badde22e6a18768ea7422
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module M9sh
4
+ class BackdropComponent < M9sh::BaseComponent
5
+ renders_one :navbar
6
+ renders_one :left_sidebar
7
+ renders_one :right_sidebar
8
+ renders_one :main_content
9
+
10
+ def initialize(
11
+ show_left_sidebar: true,
12
+ show_right_sidebar: true,
13
+ left_sidebar_width: "w-[300px]",
14
+ right_sidebar_width: "w-[300px]",
15
+ **extra_attrs
16
+ )
17
+ @show_left_sidebar = show_left_sidebar
18
+ @show_right_sidebar = show_right_sidebar
19
+ @left_sidebar_width = left_sidebar_width
20
+ @right_sidebar_width = right_sidebar_width
21
+ super(**extra_attrs)
22
+ end
23
+
24
+ def call
25
+ tag.div(**component_attrs("h-screen w-screen flex flex-col overflow-hidden"), data: { controller: "m9sh--backdrop" }) do
26
+ safe_join([
27
+ render_navbar_section,
28
+ render_overlay,
29
+ render_content_area
30
+ ].compact)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def render_navbar_section
37
+ return unless navbar?
38
+
39
+ # Render navbar with toggle buttons passed as slots
40
+ navbar.to_s.html_safe
41
+ end
42
+
43
+ def render_content_area
44
+ tag.div(class: "flex-1 flex overflow-hidden") do
45
+ safe_join([
46
+ render_left_sidebar_section,
47
+ render_main_content_section,
48
+ render_right_sidebar_section
49
+ ].compact)
50
+ end
51
+ end
52
+
53
+ def render_left_sidebar_section
54
+ return unless @show_left_sidebar && left_sidebar?
55
+
56
+ # Convert width to responsive: w-[300px] -> w-screen md:w-[300px]
57
+ desktop_width = @left_sidebar_width.start_with?('w-') ? "md:#{@left_sidebar_width}" : "md:w-[300px]"
58
+
59
+ tag.div(
60
+ class: "pt-16 w-screen #{desktop_width} flex-shrink-0 overflow-y-auto fixed md:relative left-0 md:left-auto -translate-x-full md:translate-x-0 top-0 bottom-0 md:bottom-auto z-40 md:z-auto transition-all duration-300 ease-in-out bg-transparent md:bg-background shadow-xl md:shadow-none md:border-r md:border-transparent",
61
+ data: { m9sh__backdrop_target: "leftSidebar" }
62
+ ) do
63
+ left_sidebar.to_s.html_safe
64
+ end
65
+ end
66
+
67
+ def render_main_content_section
68
+ return unless main_content?
69
+
70
+ tag.div(class: "pt-16 flex-1 overflow-auto") do
71
+ main_content.to_s.html_safe
72
+ end
73
+ end
74
+
75
+ def render_right_sidebar_section
76
+ return unless @show_right_sidebar && right_sidebar?
77
+
78
+ # Convert width to responsive: w-[300px] -> w-screen md:w-[300px]
79
+ desktop_width = @right_sidebar_width.start_with?('w-') ? "md:#{@right_sidebar_width}" : "md:w-[300px]"
80
+
81
+ tag.div(
82
+ class: "pt-16 w-screen #{desktop_width} flex-shrink-0 overflow-y-auto fixed md:relative right-0 md:right-auto translate-x-full md:translate-x-0 top-0 bottom-0 md:bottom-auto z-40 md:z-auto transition-all duration-300 ease-in-out bg-transparent md:bg-background shadow-xl md:shadow-none md:border-l md:border-transparent",
83
+ data: { m9sh__backdrop_target: "rightSidebar" }
84
+ ) do
85
+ right_sidebar.to_s.html_safe
86
+ end
87
+ end
88
+
89
+ def render_overlay
90
+ tag.div(
91
+ class: "hidden fixed top-16 left-0 right-0 bottom-0 bg-background/80 backdrop-blur-sm z-30 md:hidden",
92
+ data: {
93
+ m9sh__backdrop_target: "overlay",
94
+ action: "click->m9sh--backdrop#closeAll"
95
+ }
96
+ )
97
+ end
98
+ end
99
+ 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
+ }
@@ -107,7 +107,8 @@ module M9sh
107
107
  end
108
108
 
109
109
  def copy_file(source_file, namespace, components_path, javascript_path, force)
110
- source_path = File.join(@root_path, source_file)
110
+ # Find the source file - first try gem installation, then local dev
111
+ source_path = find_source_file(source_file)
111
112
 
112
113
  unless File.exist?(source_path)
113
114
  return { success: false, error: "Source file not found: #{source_file}" }
@@ -179,5 +180,20 @@ module M9sh
179
180
  def sync_all(options = {})
180
181
  generate_all(options.merge(force: true))
181
182
  end
183
+
184
+ # Find source file in gem directory or local development directory
185
+ def find_source_file(relative_path)
186
+ # First, try the gem installation directory
187
+ begin
188
+ spec = Gem::Specification.find_by_name('m9sh')
189
+ gem_path = File.join(spec.gem_dir, relative_path)
190
+ return gem_path if File.exist?(gem_path)
191
+ rescue Gem::MissingSpecError
192
+ # Gem not installed, fall through to local path
193
+ end
194
+
195
+ # Fall back to local development path
196
+ File.join(@root_path, relative_path)
197
+ end
182
198
  end
183
199
  end
data/lib/m9sh/registry.rb CHANGED
@@ -93,7 +93,7 @@ module M9sh
93
93
  categories = {
94
94
  "Base" => ["base", "utilities", "icon"],
95
95
  "Form Components" => ["button", "input", "label", "checkbox", "textarea", "select", "switch", "slider", "radio_group"],
96
- "Layout Components" => ["card", "table", "separator", "main"],
96
+ "Layout Components" => ["card", "table", "separator", "main", "backdrop"],
97
97
  "Feedback Components" => ["alert", "toast", "toaster", "progress", "spinner", "skeleton"],
98
98
  "Navigation Components" => ["breadcrumb", "navigation_menu", "navbar", "sidebar", "tabs", "menu"],
99
99
  "Display Components" => ["avatar", "badge", "typography"],
@@ -419,3 +419,13 @@ components:
419
419
  - icon
420
420
  - button
421
421
  - dropdown_menu
422
+
423
+ backdrop:
424
+ name: "Backdrop"
425
+ description: "Full-screen layout with navbar, sidebars, and main content area"
426
+ files:
427
+ - app/components/m9sh/backdrop_component.rb
428
+ - app/javascript/controllers/m9sh/backdrop_controller.js
429
+ dependencies:
430
+ - base
431
+ - utilities
data/lib/m9sh/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module M9sh
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: m9sh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcin Urbanski
@@ -135,6 +135,7 @@ files:
135
135
  - app/components/m9sh/alert_component.rb
136
136
  - app/components/m9sh/alert_dialog_component.rb
137
137
  - app/components/m9sh/avatar_component.rb
138
+ - app/components/m9sh/backdrop_component.rb
138
139
  - app/components/m9sh/badge_component.rb
139
140
  - app/components/m9sh/base_component.rb
140
141
  - app/components/m9sh/breadcrumb_component.rb
@@ -210,6 +211,7 @@ files:
210
211
  - app/javascript/controllers/index.js
211
212
  - app/javascript/controllers/m9sh/accordion_controller.js
212
213
  - app/javascript/controllers/m9sh/alert_dialog_controller.js
214
+ - app/javascript/controllers/m9sh/backdrop_controller.js
213
215
  - app/javascript/controllers/m9sh/collapsible_controller.js
214
216
  - app/javascript/controllers/m9sh/dialog_controller.js
215
217
  - app/javascript/controllers/m9sh/dropdown_menu_controller.js