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,260 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Select") do |c| %>
2
+ <% c.with_header(
3
+ name: "Select",
4
+ description: "Displays a list of options for the user to pick from—triggered by a select element."
5
+ ) %>
6
+
7
+ <% c.with_installation(component_name: "select") do %>
8
+ <p class="text-sm text-muted-foreground">
9
+ Copy and paste the following component file into your Rails application:
10
+ </p>
11
+
12
+ <%= render Docs::CodeBlockComponent.new(
13
+ code: <<~RUBY,
14
+ # app/components/m9sh/select_component.rb
15
+ RUBY
16
+ language: "bash"
17
+ ) %>
18
+ <% end %>
19
+
20
+ <% c.with_usage do %>
21
+ <%= render Docs::CodeBlockComponent.new(
22
+ code: select_usage_code,
23
+ language: "erb"
24
+ ) %>
25
+ <% end %>
26
+
27
+ <% c.with_examples do %>
28
+ <!-- Default Example -->
29
+ <% default_html = capture do %>
30
+ <%= render M9sh::SelectComponent.new(
31
+ name: "country",
32
+ options: ["USA", "Canada", "Mexico", "United Kingdom"]
33
+ ) %>
34
+ <% end %>
35
+
36
+ <%= render Docs::ComponentPreviewComponent.new(
37
+ title: "Default",
38
+ preview_content: default_html,
39
+ code: select_usage_code,
40
+ ai_command: select_usage_code
41
+ ) %>
42
+
43
+ <!-- With Label Example -->
44
+ <% with_label_html = capture do %>
45
+ <div class="space-y-2 max-w-xs">
46
+ <%= render M9sh::LabelComponent.new(for_id: "country") do %>
47
+ Select your country
48
+ <% end %>
49
+ <%= render M9sh::SelectComponent.new(
50
+ name: "country",
51
+ options: ["USA", "Canada", "Mexico", "United Kingdom"]
52
+ ) %>
53
+ </div>
54
+ <% end %>
55
+
56
+ <%= render Docs::ComponentPreviewComponent.new(
57
+ title: "With Label",
58
+ preview_content: with_label_html,
59
+ code: select_with_label_code,
60
+ ai_command: select_with_label_code
61
+ ) %>
62
+
63
+ <!-- With Placeholder Example -->
64
+ <% with_placeholder_html = capture do %>
65
+ <%= render M9sh::SelectComponent.new(
66
+ name: "fruit",
67
+ options: ["Apple", "Banana", "Orange", "Strawberry"],
68
+ placeholder: "Select a fruit"
69
+ ) %>
70
+ <% end %>
71
+
72
+ <%= render Docs::ComponentPreviewComponent.new(
73
+ title: "With Placeholder",
74
+ preview_content: with_placeholder_html,
75
+ code: select_with_placeholder_code,
76
+ ai_command: select_with_placeholder_code
77
+ ) %>
78
+
79
+ <!-- With Custom Options (Value/Label Pairs) Example -->
80
+ <% with_custom_options_html = capture do %>
81
+ <%= render M9sh::SelectComponent.new(
82
+ name: "country_code",
83
+ options: [
84
+ ["United States", "us"],
85
+ ["Canada", "ca"],
86
+ ["Mexico", "mx"],
87
+ ["United Kingdom", "uk"]
88
+ ]
89
+ ) %>
90
+ <% end %>
91
+
92
+ <%= render Docs::ComponentPreviewComponent.new(
93
+ title: "With Custom Options",
94
+ preview_content: with_custom_options_html,
95
+ code: select_custom_options_code,
96
+ ai_command: select_custom_options_code
97
+ ) %>
98
+
99
+ <!-- With Selected Value Example -->
100
+ <% with_selected_html = capture do %>
101
+ <%= render M9sh::SelectComponent.new(
102
+ name: "language",
103
+ options: [
104
+ ["English", "en"],
105
+ ["Spanish", "es"],
106
+ ["French", "fr"],
107
+ ["German", "de"]
108
+ ],
109
+ selected: "es"
110
+ ) %>
111
+ <% end %>
112
+
113
+ <%= render Docs::ComponentPreviewComponent.new(
114
+ title: "With Selected Value",
115
+ preview_content: with_selected_html,
116
+ code: select_with_selected_code,
117
+ ai_command: select_with_selected_code
118
+ ) %>
119
+
120
+ <!-- Disabled State Example -->
121
+ <% disabled_html = capture do %>
122
+ <div class="space-y-4 max-w-xs">
123
+ <%= render M9sh::SelectComponent.new(
124
+ name: "disabled",
125
+ options: ["Option 1", "Option 2", "Option 3"],
126
+ disabled: true
127
+ ) %>
128
+ <%= render M9sh::SelectComponent.new(
129
+ name: "disabled_with_value",
130
+ options: ["Option 1", "Option 2", "Option 3"],
131
+ selected: "Option 2",
132
+ disabled: true
133
+ ) %>
134
+ </div>
135
+ <% end %>
136
+
137
+ <%= render Docs::ComponentPreviewComponent.new(
138
+ title: "Disabled State",
139
+ preview_content: disabled_html,
140
+ code: select_disabled_code,
141
+ ai_command: select_disabled_code
142
+ ) %>
143
+
144
+ <!-- Form Integration Example -->
145
+ <% form_html = capture do %>
146
+ <div class="border rounded-lg p-6 max-w-md">
147
+ <h3 class="text-lg font-semibold mb-4">User Information</h3>
148
+ <div class="space-y-4">
149
+ <div class="space-y-2">
150
+ <%= render M9sh::LabelComponent.new(for_id: "country") do %>
151
+ Country
152
+ <% end %>
153
+ <%= render M9sh::SelectComponent.new(
154
+ name: "user[country]",
155
+ options: [
156
+ ["United States", "us"],
157
+ ["Canada", "ca"],
158
+ ["Mexico", "mx"],
159
+ ["United Kingdom", "uk"]
160
+ ],
161
+ placeholder: "Select your country"
162
+ ) %>
163
+ </div>
164
+ <div class="space-y-2">
165
+ <%= render M9sh::LabelComponent.new(for_id: "timezone") do %>
166
+ Timezone
167
+ <% end %>
168
+ <%= render M9sh::SelectComponent.new(
169
+ name: "user[timezone]",
170
+ options: [
171
+ ["Eastern Time (ET)", "est"],
172
+ ["Central Time (CT)", "cst"],
173
+ ["Mountain Time (MT)", "mst"],
174
+ ["Pacific Time (PT)", "pst"]
175
+ ],
176
+ selected: "est"
177
+ ) %>
178
+ </div>
179
+ <div class="space-y-2">
180
+ <%= render M9sh::LabelComponent.new(for_id: "language") do %>
181
+ Preferred Language
182
+ <% end %>
183
+ <%= render M9sh::SelectComponent.new(
184
+ name: "user[language]",
185
+ options: ["English", "Spanish", "French", "German"],
186
+ selected: "English"
187
+ ) %>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ <% end %>
192
+
193
+ <%= render Docs::ComponentPreviewComponent.new(
194
+ title: "Form Integration",
195
+ preview_content: form_html,
196
+ code: select_form_integration_code,
197
+ ai_command: select_form_integration_code
198
+ ) %>
199
+ <% end %>
200
+
201
+ <% c.with_api do %>
202
+ <%= render Docs::PropTableComponent.new(
203
+ props: [
204
+ {
205
+ name: "name",
206
+ type: "String",
207
+ default: "nil",
208
+ description: "The name attribute for the select element (used for form submissions)"
209
+ },
210
+ {
211
+ name: "options",
212
+ type: "Array",
213
+ default: "[]",
214
+ description: "Array of options. Can be an array of strings or an array of [label, value] pairs"
215
+ },
216
+ {
217
+ name: "selected",
218
+ type: "String",
219
+ default: "nil",
220
+ description: "The value of the option that should be selected by default"
221
+ },
222
+ {
223
+ name: "placeholder",
224
+ type: "String",
225
+ default: "nil",
226
+ description: "Optional placeholder text shown when no option is selected"
227
+ },
228
+ {
229
+ name: "disabled",
230
+ type: "Boolean",
231
+ default: "false",
232
+ description: "Whether the select element is disabled"
233
+ }
234
+ ]
235
+ ) %>
236
+
237
+ <div class="mt-6 space-y-2">
238
+ <h3 class="text-lg font-semibold">Options Format</h3>
239
+ <p class="text-sm text-muted-foreground">
240
+ The <code>options</code> parameter accepts two formats:
241
+ </p>
242
+ <ul class="list-disc list-inside space-y-2 text-sm text-muted-foreground ml-4">
243
+ <li><strong>Simple array:</strong> <code>["Option 1", "Option 2"]</code> - Both label and value will be the same</li>
244
+ <li><strong>Label/Value pairs:</strong> <code>[["Label 1", "value1"], ["Label 2", "value2"]]</code> - Allows different display text and values</li>
245
+ </ul>
246
+ </div>
247
+
248
+ <div class="mt-6 space-y-2">
249
+ <h3 class="text-lg font-semibold">Additional Notes</h3>
250
+ <ul class="list-disc list-inside space-y-2 text-sm text-muted-foreground">
251
+ <li>The select component uses native HTML select elements for better accessibility and mobile support</li>
252
+ <li>The component automatically handles the selected state based on the <code>selected</code> parameter</li>
253
+ <li>When a placeholder is provided, an empty option is added at the beginning</li>
254
+ <li>All additional HTML attributes can be passed via extra_attrs and will be applied to the select element</li>
255
+ <li>The component includes proper focus and disabled state styling</li>
256
+ <li>Works seamlessly with Rails form helpers and strong parameters</li>
257
+ </ul>
258
+ </div>
259
+ <% end %>
260
+ <% end %>
@@ -0,0 +1,162 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Separator") do |page| %>
2
+ <% page.with_header(
3
+ name: "Separator",
4
+ description: "Visually or semantically separates content with a line."
5
+ ) %>
6
+
7
+ <% page.with_installation(component_name: "separator") %>
8
+
9
+ <% page.with_usage do %>
10
+ <%= render Docs::CodeBlockComponent.new(
11
+ code: separator_usage_code,
12
+ language: "erb"
13
+ ) %>
14
+ <% end %>
15
+
16
+ <% page.with_examples do %>
17
+ <!-- Default Example -->
18
+ <% default_html = capture do %>
19
+ <div class="w-full max-w-md">
20
+ <div class="space-y-1">
21
+ <h4 class="text-sm font-medium leading-none">Radix Primitives</h4>
22
+ <p class="text-sm text-muted-foreground">
23
+ An open-source UI component library.
24
+ </p>
25
+ </div>
26
+ <%= render M9sh::SeparatorComponent.new(class: "my-4") %>
27
+ <div class="flex h-5 items-center space-x-4 text-sm">
28
+ <div>Blog</div>
29
+ <%= render M9sh::SeparatorComponent.new(orientation: :vertical) %>
30
+ <div>Docs</div>
31
+ <%= render M9sh::SeparatorComponent.new(orientation: :vertical) %>
32
+ <div>Source</div>
33
+ </div>
34
+ </div>
35
+ <% end %>
36
+
37
+ <%= render Docs::ComponentPreviewComponent.new(
38
+ title: "Default",
39
+ preview_content: default_html,
40
+ code: separator_horizontal_code,
41
+ ai_command: separator_horizontal_code
42
+ ) %>
43
+
44
+ <!-- Text Sections Example -->
45
+ <% sections_html = capture do %>
46
+ <div class="w-full max-w-md">
47
+ <div class="space-y-1">
48
+ <h4 class="text-sm font-medium leading-none">Introduction</h4>
49
+ <p class="text-sm text-muted-foreground">
50
+ An introduction to the product and its features.
51
+ </p>
52
+ </div>
53
+ <%= render M9sh::SeparatorComponent.new(class: "my-4") %>
54
+ <div class="space-y-1">
55
+ <h4 class="text-sm font-medium leading-none">Getting Started</h4>
56
+ <p class="text-sm text-muted-foreground">
57
+ Learn how to install and configure the components.
58
+ </p>
59
+ </div>
60
+ </div>
61
+ <% end %>
62
+
63
+ <%= render Docs::ComponentPreviewComponent.new(
64
+ title: "Text Sections",
65
+ preview_content: sections_html,
66
+ code: separator_text_sections_code,
67
+ ai_command: separator_text_sections_code
68
+ ) %>
69
+
70
+ <!-- Breadcrumb Example -->
71
+ <% breadcrumb_html = capture do %>
72
+ <div class="flex items-center text-sm text-muted-foreground">
73
+ <a href="#" class="hover:text-foreground transition-colors">Home</a>
74
+ <%= render M9sh::SeparatorComponent.new(orientation: :vertical, class: "mx-2 h-4") %>
75
+ <a href="#" class="hover:text-foreground transition-colors">Components</a>
76
+ <%= render M9sh::SeparatorComponent.new(orientation: :vertical, class: "mx-2 h-4") %>
77
+ <span class="text-foreground font-medium">Separator</span>
78
+ </div>
79
+ <% end %>
80
+
81
+ <%= render Docs::ComponentPreviewComponent.new(
82
+ title: "Breadcrumb Navigation",
83
+ preview_content: breadcrumb_html,
84
+ code: separator_breadcrumb_code,
85
+ ai_command: separator_breadcrumb_code
86
+ ) %>
87
+
88
+ <!-- In Card Example -->
89
+ <% card_html = capture do %>
90
+ <%= render M9sh::CardComponent.new(class: "w-full max-w-sm") do |card| %>
91
+ <% card.with_header do |header| %>
92
+ <% header.with_title { "Account Settings" } %>
93
+ <% header.with_description { "Manage your account preferences and settings." } %>
94
+ <% end %>
95
+ <% card.with_body do %>
96
+ <div class="space-y-4">
97
+ <div class="space-y-2">
98
+ <label class="text-sm font-medium leading-none">Email</label>
99
+ <p class="text-sm text-muted-foreground">m@example.com</p>
100
+ </div>
101
+ <%= render M9sh::SeparatorComponent.new %>
102
+ <div class="space-y-2">
103
+ <label class="text-sm font-medium leading-none">Plan</label>
104
+ <p class="text-sm text-muted-foreground">Pro</p>
105
+ </div>
106
+ </div>
107
+ <% end %>
108
+ <% card.with_footer do %>
109
+ <%= render M9sh::ButtonComponent.new(variant: :outline, class: "w-full") { "Manage Account" } %>
110
+ <% end %>
111
+ <% end %>
112
+ <% end %>
113
+
114
+ <%= render Docs::ComponentPreviewComponent.new(
115
+ title: "In Card",
116
+ preview_content: card_html,
117
+ code: separator_in_card_code,
118
+ ai_command: separator_in_card_code
119
+ ) %>
120
+ <% end %>
121
+
122
+ <% page.with_api do %>
123
+ <%= render Docs::PropTableComponent.new(
124
+ props: [
125
+ {
126
+ name: "orientation",
127
+ type: "Symbol",
128
+ default: ":horizontal",
129
+ description: "The orientation of the separator. Options: :horizontal, :vertical"
130
+ },
131
+ {
132
+ name: "decorative",
133
+ type: "Boolean",
134
+ default: "true",
135
+ description: "Whether the separator is purely decorative. When true, role is set to 'none', when false it's 'separator'"
136
+ }
137
+ ]
138
+ ) %>
139
+
140
+ <div class="mt-6 space-y-3 text-sm">
141
+ <h3 class="text-lg font-semibold">Accessibility</h3>
142
+ <p>
143
+ The Separator component follows WAI-ARIA separator role specifications:
144
+ </p>
145
+
146
+ <ul class="list-disc list-inside space-y-2 text-muted-foreground">
147
+ <li>
148
+ By default, the separator is decorative (<code class="text-xs bg-muted px-1 py-0.5 rounded">role="none"</code>) and removed from the accessibility tree
149
+ </li>
150
+ <li>
151
+ Set <code class="text-xs bg-muted px-1 py-0.5 rounded">decorative: false</code> to make it a semantic separator (<code class="text-xs bg-muted px-1 py-0.5 rounded">role="separator"</code>)
152
+ </li>
153
+ <li>
154
+ The <code class="text-xs bg-muted px-1 py-0.5 rounded">aria-orientation</code> attribute is automatically set based on the orientation parameter
155
+ </li>
156
+ <li>
157
+ Use semantic separators when the division is meaningful to understanding the page structure
158
+ </li>
159
+ </ul>
160
+ </div>
161
+ <% end %>
162
+ <% end %>
@@ -0,0 +1,270 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Sheet") do |page| %>
2
+ <% page.with_header(
3
+ name: "Sheet",
4
+ description: "Extends the Dialog component to display content that slides in from the edge of the screen."
5
+ ) %>
6
+
7
+ <% page.with_installation(component_name: "sheet") %>
8
+
9
+ <% page.with_usage do %>
10
+ <%= render Docs::CodeBlockComponent.new(
11
+ code: sheet_usage_code,
12
+ language: "erb"
13
+ ) %>
14
+ <% end %>
15
+
16
+ <% page.with_examples do %>
17
+ <!-- Default Example -->
18
+ <% default_sheet_html = capture do %>
19
+ <%= render M9sh::SheetComponent.new do |sheet| %>
20
+ <% sheet.with_trigger do %>
21
+ <%= render M9sh::ButtonComponent.new do %>
22
+ Open Sheet
23
+ <% end %>
24
+ <% end %>
25
+ <% sheet.with_title do %>
26
+ Sheet Title
27
+ <% end %>
28
+ <% sheet.with_description do %>
29
+ Make changes to your profile here. Click save when you're done.
30
+ <% end %>
31
+ <% sheet.with_sheet_content do %>
32
+ <div class="space-y-4">
33
+ <p class="text-sm">
34
+ This is a sheet component that slides in from the right side of the screen by default.
35
+ It provides a clean way to display additional content without navigating away from the current page.
36
+ </p>
37
+ <p class="text-sm text-muted-foreground">
38
+ Click the close button or outside the sheet to dismiss it.
39
+ </p>
40
+ </div>
41
+ <% end %>
42
+ <% end %>
43
+ <% end %>
44
+
45
+ <%= render Docs::ComponentPreviewComponent.new(
46
+ title: "Default",
47
+ preview_content: default_sheet_html,
48
+ code: sheet_usage_code,
49
+ ai_command: sheet_usage_code
50
+ ) %>
51
+
52
+ <!-- Different Sides Example -->
53
+ <% sides_sheet_html = capture do %>
54
+ <div class="flex flex-wrap gap-2">
55
+ <%= render M9sh::SheetComponent.new(side: "left") do |sheet| %>
56
+ <% sheet.with_trigger do %>
57
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
58
+ Open Left
59
+ <% end %>
60
+ <% end %>
61
+ <% sheet.with_title do %>
62
+ Left Sheet
63
+ <% end %>
64
+ <% sheet.with_description do %>
65
+ This sheet slides in from the left side.
66
+ <% end %>
67
+ <% sheet.with_sheet_content do %>
68
+ <p class="text-sm">Content from the left side.</p>
69
+ <% end %>
70
+ <% end %>
71
+
72
+ <%= render M9sh::SheetComponent.new(side: "right") do |sheet| %>
73
+ <% sheet.with_trigger do %>
74
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
75
+ Open Right
76
+ <% end %>
77
+ <% end %>
78
+ <% sheet.with_title do %>
79
+ Right Sheet
80
+ <% end %>
81
+ <% sheet.with_description do %>
82
+ This sheet slides in from the right side.
83
+ <% end %>
84
+ <% sheet.with_sheet_content do %>
85
+ <p class="text-sm">Content from the right side.</p>
86
+ <% end %>
87
+ <% end %>
88
+
89
+ <%= render M9sh::SheetComponent.new(side: "top") do |sheet| %>
90
+ <% sheet.with_trigger do %>
91
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
92
+ Open Top
93
+ <% end %>
94
+ <% end %>
95
+ <% sheet.with_title do %>
96
+ Top Sheet
97
+ <% end %>
98
+ <% sheet.with_description do %>
99
+ This sheet slides in from the top.
100
+ <% end %>
101
+ <% sheet.with_sheet_content do %>
102
+ <p class="text-sm">Content from the top.</p>
103
+ <% end %>
104
+ <% end %>
105
+
106
+ <%= render M9sh::SheetComponent.new(side: "bottom") do |sheet| %>
107
+ <% sheet.with_trigger do %>
108
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
109
+ Open Bottom
110
+ <% end %>
111
+ <% end %>
112
+ <% sheet.with_title do %>
113
+ Bottom Sheet
114
+ <% end %>
115
+ <% sheet.with_description do %>
116
+ This sheet slides in from the bottom.
117
+ <% end %>
118
+ <% sheet.with_sheet_content do %>
119
+ <p class="text-sm">Content from the bottom.</p>
120
+ <% end %>
121
+ <% end %>
122
+ </div>
123
+ <% end %>
124
+
125
+ <%= render Docs::ComponentPreviewComponent.new(
126
+ title: "From Different Sides",
127
+ preview_content: sides_sheet_html,
128
+ code: sheet_side_code,
129
+ ai_command: sheet_side_code
130
+ ) %>
131
+
132
+ <!-- With Form Example -->
133
+ <% form_sheet_html = capture do %>
134
+ <%= render M9sh::SheetComponent.new do |sheet| %>
135
+ <% sheet.with_trigger do %>
136
+ <%= render M9sh::ButtonComponent.new do %>
137
+ Edit Profile
138
+ <% end %>
139
+ <% end %>
140
+ <% sheet.with_title do %>
141
+ Edit Profile
142
+ <% end %>
143
+ <% sheet.with_description do %>
144
+ Make changes to your profile here. Click save when you're done.
145
+ <% end %>
146
+ <% sheet.with_sheet_content do %>
147
+ <div class="space-y-4 py-4">
148
+ <div class="space-y-2">
149
+ <%= render M9sh::LabelComponent.new(for_id: "sheet-name") do %>
150
+ Name
151
+ <% end %>
152
+ <%= render M9sh::InputComponent.new(
153
+ id: "sheet-name",
154
+ type: "text",
155
+ placeholder: "Enter your name",
156
+ value: "John Doe"
157
+ ) %>
158
+ </div>
159
+ <div class="space-y-2">
160
+ <%= render M9sh::LabelComponent.new(for_id: "sheet-email") do %>
161
+ Email
162
+ <% end %>
163
+ <%= render M9sh::InputComponent.new(
164
+ id: "sheet-email",
165
+ type: "email",
166
+ placeholder: "Enter your email",
167
+ value: "john@example.com"
168
+ ) %>
169
+ </div>
170
+ <div class="space-y-2">
171
+ <%= render M9sh::LabelComponent.new(for_id: "sheet-bio") do %>
172
+ Bio
173
+ <% end %>
174
+ <%= render M9sh::TextareaComponent.new(
175
+ id: "sheet-bio",
176
+ name: "bio",
177
+ placeholder: "Tell us about yourself"
178
+ ) %>
179
+ </div>
180
+ <div class="flex justify-end gap-2 pt-4">
181
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
182
+ Cancel
183
+ <% end %>
184
+ <%= render M9sh::ButtonComponent.new do %>
185
+ Save Changes
186
+ <% end %>
187
+ </div>
188
+ </div>
189
+ <% end %>
190
+ <% end %>
191
+ <% end %>
192
+
193
+ <%= render Docs::ComponentPreviewComponent.new(
194
+ title: "With Form",
195
+ preview_content: form_sheet_html,
196
+ code: sheet_with_form_code,
197
+ ai_command: sheet_with_form_code
198
+ ) %>
199
+ <% end %>
200
+
201
+ <% page.with_api do %>
202
+ <h3 class="text-lg font-semibold">Sheet Component</h3>
203
+
204
+ <%= render Docs::PropTableComponent.new(
205
+ props: [
206
+ {
207
+ name: "side",
208
+ type: "String",
209
+ default: '"right"',
210
+ description: "The side of the screen from which the sheet slides in. Options: 'left', 'right', 'top', 'bottom'"
211
+ }
212
+ ]
213
+ ) %>
214
+
215
+ <h3 class="text-lg font-semibold mt-6">Sheet Slots</h3>
216
+ <%= render Docs::PropTableComponent.new(
217
+ props: [
218
+ {
219
+ name: "trigger",
220
+ type: "Slot",
221
+ default: nil,
222
+ description: "The element that triggers the sheet to open. Typically a button or link."
223
+ },
224
+ {
225
+ name: "title",
226
+ type: "Slot",
227
+ default: nil,
228
+ description: "The title displayed in the sheet header."
229
+ },
230
+ {
231
+ name: "description",
232
+ type: "Slot",
233
+ default: nil,
234
+ description: "Optional description text displayed below the title in the header."
235
+ },
236
+ {
237
+ name: "sheet_content",
238
+ type: "Slot",
239
+ default: nil,
240
+ description: "The main content area of the sheet. This area is scrollable if content overflows."
241
+ }
242
+ ]
243
+ ) %>
244
+
245
+ <div class="mt-6 space-y-2">
246
+ <h3 class="text-lg font-semibold">Features</h3>
247
+ <ul class="list-disc list-inside text-sm text-muted-foreground space-y-1">
248
+ <li>Slides in from any edge of the screen (left, right, top, or bottom)</li>
249
+ <li>Includes an overlay that dims the background content</li>
250
+ <li>Automatically adds a close button in the top-right corner</li>
251
+ <li>Can be dismissed by clicking the overlay, close button, or pressing Escape</li>
252
+ <li>Smooth animations for opening and closing transitions</li>
253
+ <li>Scrollable content area when content exceeds viewport height</li>
254
+ <li>Fixed width for side sheets (left/right) and full width for top/bottom sheets</li>
255
+ <li>Responsive design with appropriate sizing on mobile devices</li>
256
+ </ul>
257
+ </div>
258
+
259
+ <div class="mt-6 space-y-2">
260
+ <h3 class="text-lg font-semibold">Notes</h3>
261
+ <ul class="list-disc list-inside text-sm text-muted-foreground space-y-1">
262
+ <li>The sheet uses Stimulus controllers for interactive behavior</li>
263
+ <li>Side sheets (left/right) have a maximum width of sm (24rem) on larger screens</li>
264
+ <li>Top and bottom sheets span the full width of the screen</li>
265
+ <li>The overlay prevents interaction with background content while the sheet is open</li>
266
+ <li>All slots are optional except for trigger - a sheet must have a way to be opened</li>
267
+ </ul>
268
+ </div>
269
+ <% end %>
270
+ <% end %>