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,267 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Popover") do |page| %>
2
+ <% page.with_header(
3
+ name: "Popover",
4
+ description: "Displays rich content in a portal, triggered by a button or other element."
5
+ ) %>
6
+
7
+ <% page.with_installation(component_name: "popover") %>
8
+
9
+ <% page.with_usage do %>
10
+ <%= render Docs::CodeBlockComponent.new(
11
+ code: popover_usage_code,
12
+ language: "erb"
13
+ ) %>
14
+ <% end %>
15
+
16
+ <% page.with_examples do %>
17
+ <!-- Default Example -->
18
+ <% default_popover_html = capture do %>
19
+ <%= render M9sh::PopoverComponent.new do |popover| %>
20
+ <% popover.with_trigger do %>
21
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
22
+ Open Popover
23
+ <% end %>
24
+ <% end %>
25
+ <% popover.with_popover_content do %>
26
+ <div class="space-y-2">
27
+ <h4 class="font-medium text-sm">Popover Content</h4>
28
+ <p class="text-sm text-muted-foreground">
29
+ This is a simple popover with some text content. Popovers are great for displaying additional information without navigating away.
30
+ </p>
31
+ </div>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
35
+
36
+ <%= render Docs::ComponentPreviewComponent.new(
37
+ title: "Default",
38
+ preview_content: default_popover_html,
39
+ code: popover_usage_code,
40
+ ai_command: popover_usage_code
41
+ ) %>
42
+
43
+ <!-- With Form Example -->
44
+ <% form_popover_html = capture do %>
45
+ <%= render M9sh::PopoverComponent.new do |popover| %>
46
+ <% popover.with_trigger do %>
47
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
48
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2">
49
+ <circle cx="12" cy="12" r="3"/>
50
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
51
+ </svg>
52
+ Settings
53
+ <% end %>
54
+ <% end %>
55
+ <% popover.with_popover_content do %>
56
+ <div class="space-y-4">
57
+ <h4 class="font-medium text-sm">Dimensions</h4>
58
+ <div class="space-y-2">
59
+ <%= render M9sh::LabelComponent.new(for_id: "popover-width") do %>
60
+ Width
61
+ <% end %>
62
+ <%= render M9sh::InputComponent.new(
63
+ id: "popover-width",
64
+ type: "number",
65
+ placeholder: "100",
66
+ value: "300"
67
+ ) %>
68
+ </div>
69
+ <div class="space-y-2">
70
+ <%= render M9sh::LabelComponent.new(for_id: "popover-height") do %>
71
+ Height
72
+ <% end %>
73
+ <%= render M9sh::InputComponent.new(
74
+ id: "popover-height",
75
+ type: "number",
76
+ placeholder: "100",
77
+ value: "200"
78
+ ) %>
79
+ </div>
80
+ <%= render M9sh::ButtonComponent.new(class: "w-full") do %>
81
+ Save Changes
82
+ <% end %>
83
+ </div>
84
+ <% end %>
85
+ <% end %>
86
+ <% end %>
87
+
88
+ <%= render Docs::ComponentPreviewComponent.new(
89
+ title: "With Form",
90
+ preview_content: form_popover_html,
91
+ code: popover_with_form_code,
92
+ ai_command: popover_with_form_code
93
+ ) %>
94
+
95
+ <!-- With Rich Content Example -->
96
+ <% rich_popover_html = capture do %>
97
+ <%= render M9sh::PopoverComponent.new do |popover| %>
98
+ <% popover.with_trigger do %>
99
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
100
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2">
101
+ <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
102
+ <circle cx="12" cy="7" r="4"/>
103
+ </svg>
104
+ User Info
105
+ <% end %>
106
+ <% end %>
107
+ <% popover.with_popover_content do %>
108
+ <div class="space-y-3">
109
+ <div class="flex items-center gap-3">
110
+ <div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center font-semibold text-primary">
111
+ JD
112
+ </div>
113
+ <div>
114
+ <h4 class="font-semibold text-sm">John Doe</h4>
115
+ <p class="text-xs text-muted-foreground">john@example.com</p>
116
+ </div>
117
+ </div>
118
+ <div class="border-t pt-3">
119
+ <p class="text-xs text-muted-foreground mb-2">Bio</p>
120
+ <p class="text-sm">Software developer passionate about building great user experiences.</p>
121
+ </div>
122
+ <div class="flex gap-2 pt-2">
123
+ <%= render M9sh::ButtonComponent.new(variant: :outline, size: :sm, class: "flex-1") do %>
124
+ View Profile
125
+ <% end %>
126
+ <%= render M9sh::ButtonComponent.new(size: :sm, class: "flex-1") do %>
127
+ Message
128
+ <% end %>
129
+ </div>
130
+ </div>
131
+ <% end %>
132
+ <% end %>
133
+ <% end %>
134
+
135
+ <%= render Docs::ComponentPreviewComponent.new(
136
+ title: "With Rich Content",
137
+ preview_content: rich_popover_html,
138
+ code: popover_rich_content_code,
139
+ ai_command: popover_rich_content_code
140
+ ) %>
141
+
142
+ <!-- Different Positions Example -->
143
+ <% positions_popover_html = capture do %>
144
+ <div class="flex flex-col items-center gap-4">
145
+ <div>
146
+ <%= render M9sh::PopoverComponent.new(side: "top") do |popover| %>
147
+ <% popover.with_trigger do %>
148
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
149
+ Top
150
+ <% end %>
151
+ <% end %>
152
+ <% popover.with_popover_content do %>
153
+ <p class="text-sm">This popover appears at the top.</p>
154
+ <% end %>
155
+ <% end %>
156
+ </div>
157
+ <div class="flex gap-4">
158
+ <%= render M9sh::PopoverComponent.new(side: "left") do |popover| %>
159
+ <% popover.with_trigger do %>
160
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
161
+ Left
162
+ <% end %>
163
+ <% end %>
164
+ <% popover.with_popover_content do %>
165
+ <p class="text-sm">This popover appears on the left.</p>
166
+ <% end %>
167
+ <% end %>
168
+
169
+ <%= render M9sh::PopoverComponent.new(side: "right") do |popover| %>
170
+ <% popover.with_trigger do %>
171
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
172
+ Right
173
+ <% end %>
174
+ <% end %>
175
+ <% popover.with_popover_content do %>
176
+ <p class="text-sm">This popover appears on the right.</p>
177
+ <% end %>
178
+ <% end %>
179
+ </div>
180
+ <div>
181
+ <%= render M9sh::PopoverComponent.new(side: "bottom") do |popover| %>
182
+ <% popover.with_trigger do %>
183
+ <%= render M9sh::ButtonComponent.new(variant: :outline) do %>
184
+ Bottom
185
+ <% end %>
186
+ <% end %>
187
+ <% popover.with_popover_content do %>
188
+ <p class="text-sm">This popover appears at the bottom.</p>
189
+ <% end %>
190
+ <% end %>
191
+ </div>
192
+ </div>
193
+ <% end %>
194
+
195
+ <%= render Docs::ComponentPreviewComponent.new(
196
+ title: "Different Positions",
197
+ preview_content: positions_popover_html,
198
+ code: nil,
199
+ ai_command: nil
200
+ ) %>
201
+ <% end %>
202
+
203
+ <% page.with_api do %>
204
+ <h3 class="text-lg font-semibold">Popover Component</h3>
205
+
206
+ <%= render Docs::PropTableComponent.new(
207
+ props: [
208
+ {
209
+ name: "align",
210
+ type: "String",
211
+ default: '"center"',
212
+ description: "The alignment of the popover relative to the trigger. Options: 'start', 'center', 'end'"
213
+ },
214
+ {
215
+ name: "side",
216
+ type: "String",
217
+ default: '"bottom"',
218
+ description: "The side of the trigger where the popover appears. Options: 'top', 'right', 'bottom', 'left'"
219
+ }
220
+ ]
221
+ ) %>
222
+
223
+ <h3 class="text-lg font-semibold mt-6">Popover Slots</h3>
224
+ <%= render Docs::PropTableComponent.new(
225
+ props: [
226
+ {
227
+ name: "trigger",
228
+ type: "Slot",
229
+ default: nil,
230
+ description: "The element that triggers the popover to open. Typically a button or link."
231
+ },
232
+ {
233
+ name: "popover_content",
234
+ type: "Slot",
235
+ default: nil,
236
+ description: "The content displayed inside the popover. Can contain any HTML including forms, text, images, etc."
237
+ }
238
+ ]
239
+ ) %>
240
+
241
+ <div class="mt-6 space-y-2">
242
+ <h3 class="text-lg font-semibold">Features</h3>
243
+ <ul class="list-disc list-inside text-sm text-muted-foreground space-y-1">
244
+ <li>Can be positioned on any side of the trigger (top, right, bottom, left)</li>
245
+ <li>Supports alignment options for fine-tuned positioning</li>
246
+ <li>Toggles on click - clicking the trigger again closes the popover</li>
247
+ <li>Automatically closes when clicking outside the popover</li>
248
+ <li>Smooth fade and zoom animations for opening and closing</li>
249
+ <li>Fixed width (w-72 / 18rem) by default for consistent sizing</li>
250
+ <li>Supports rich content including forms, images, and interactive elements</li>
251
+ <li>Includes subtle border and shadow for visual depth</li>
252
+ </ul>
253
+ </div>
254
+
255
+ <div class="mt-6 space-y-2">
256
+ <h3 class="text-lg font-semibold">Notes</h3>
257
+ <ul class="list-disc list-inside text-sm text-muted-foreground space-y-1">
258
+ <li>The popover uses Stimulus controllers for interactive behavior</li>
259
+ <li>Click outside or click the trigger again to close the popover</li>
260
+ <li>The popover content has a fixed width - use custom classes to override if needed</li>
261
+ <li>Both trigger and popover_content slots are required for the component to function</li>
262
+ <li>The popover is positioned absolutely relative to its inline-block container</li>
263
+ <li>Consider using Sheet component for larger amounts of content or complex interactions</li>
264
+ </ul>
265
+ </div>
266
+ <% end %>
267
+ <% end %>
@@ -0,0 +1,107 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Progress") do |page| %>
2
+ <% page.with_header(
3
+ name: "Progress",
4
+ description: "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar."
5
+ ) %>
6
+
7
+ <% page.with_installation(component_name: "progress") %>
8
+
9
+ <% page.with_usage do %>
10
+ <%= render Docs::CodeBlockComponent.new(
11
+ code: progress_usage_code,
12
+ language: "erb"
13
+ ) %>
14
+ <% end %>
15
+
16
+ <% page.with_examples do %>
17
+ <!-- Default Example -->
18
+ <% default_html = capture do %>
19
+ <%= render M9sh::ProgressComponent.new(value: 60) %>
20
+ <% end %>
21
+
22
+ <%= render Docs::ComponentPreviewComponent.new(
23
+ title: "Default",
24
+ preview_content: default_html,
25
+ code: progress_usage_code,
26
+ ai_command: progress_usage_code
27
+ ) %>
28
+
29
+ <!-- Different Values Example -->
30
+ <% values_html = capture do %>
31
+ <div class="space-y-4 w-full">
32
+ <div>
33
+ <p class="text-sm text-muted-foreground mb-2">25%</p>
34
+ <%= render M9sh::ProgressComponent.new(value: 25) %>
35
+ </div>
36
+ <div>
37
+ <p class="text-sm text-muted-foreground mb-2">50%</p>
38
+ <%= render M9sh::ProgressComponent.new(value: 50) %>
39
+ </div>
40
+ <div>
41
+ <p class="text-sm text-muted-foreground mb-2">75%</p>
42
+ <%= render M9sh::ProgressComponent.new(value: 75) %>
43
+ </div>
44
+ <div>
45
+ <p class="text-sm text-muted-foreground mb-2">100%</p>
46
+ <%= render M9sh::ProgressComponent.new(value: 100) %>
47
+ </div>
48
+ </div>
49
+ <% end %>
50
+
51
+ <%= render Docs::ComponentPreviewComponent.new(
52
+ title: "Different Values",
53
+ preview_content: values_html,
54
+ code: progress_values_code,
55
+ ai_command: progress_values_code
56
+ ) %>
57
+
58
+ <!-- Indeterminate Example -->
59
+ <% indeterminate_html = capture do %>
60
+ <div class="space-y-2 w-full">
61
+ <%= render M9sh::ProgressComponent.new(value: 0) %>
62
+ <p class="text-sm text-muted-foreground">Loading...</p>
63
+ </div>
64
+ <% end %>
65
+
66
+ <%= render Docs::ComponentPreviewComponent.new(
67
+ title: "Indeterminate",
68
+ preview_content: indeterminate_html,
69
+ code: progress_indeterminate_code,
70
+ ai_command: progress_indeterminate_code
71
+ ) %>
72
+ <% end %>
73
+
74
+ <% page.with_api do %>
75
+ <%= render Docs::PropTableComponent.new(
76
+ props: [
77
+ {
78
+ name: "value",
79
+ type: "Number",
80
+ default: "0",
81
+ description: "The current progress value. Should be between 0 and max."
82
+ },
83
+ {
84
+ name: "max",
85
+ type: "Number",
86
+ default: "100",
87
+ description: "The maximum progress value. Used to calculate the percentage."
88
+ }
89
+ ]
90
+ ) %>
91
+
92
+ <div class="mt-6 space-y-2">
93
+ <h3 class="text-lg font-semibold">Accessibility</h3>
94
+ <ul class="list-disc list-inside space-y-1 text-sm text-muted-foreground">
95
+ <li>Uses proper ARIA attributes (<code>role="progressbar"</code>, <code>aria-valuenow</code>, <code>aria-valuemin</code>, <code>aria-valuemax</code>)</li>
96
+ <li>Works with screen readers to announce progress updates</li>
97
+ <li>Follows WAI-ARIA design pattern for progress bars</li>
98
+ </ul>
99
+ </div>
100
+
101
+ <div class="mt-4 p-4 border rounded-lg bg-muted">
102
+ <p class="text-sm text-muted-foreground">
103
+ <strong>Note:</strong> The progress component automatically calculates the percentage based on the <code class="text-sm">value</code> and <code class="text-sm">max</code> props. The visual indicator will fill from left to right as the value increases.
104
+ </p>
105
+ </div>
106
+ <% end %>
107
+ <% end %>
@@ -0,0 +1,209 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Radio Group") do |page| %>
2
+ <% page.with_header(name: "Radio Group", description: "A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.") %>
3
+ <% page.with_installation(component_name: "radio_group") %>
4
+ <% page.with_usage do %>
5
+ <%= render Docs::CodeBlockComponent.new(code: radio_group_usage_code, language: "erb") %>
6
+ <% end %>
7
+ <% page.with_examples do %>
8
+ <!-- Default Example -->
9
+ <% default_html = capture do %>
10
+ <%= render M9sh::RadioGroupComponent.new(name: "default") do |radio_group| %>
11
+ <% radio_group.with_item(value: "option1", label: "Option 1", checked: true) %>
12
+ <% radio_group.with_item(value: "option2", label: "Option 2") %>
13
+ <% radio_group.with_item(value: "option3", label: "Option 3") %>
14
+ <% end %>
15
+ <% end %>
16
+
17
+ <%= render Docs::ComponentPreviewComponent.new(
18
+ title: "Default",
19
+ preview_content: default_html,
20
+ code: radio_group_default_code,
21
+ ai_command: radio_group_default_code
22
+ ) %>
23
+
24
+ <!-- With Labels Example -->
25
+ <% with_labels_html = capture do %>
26
+ <div class="space-y-4">
27
+ <%= render M9sh::LabelComponent.new do %>
28
+ Select your display preference
29
+ <% end %>
30
+ <%= render M9sh::RadioGroupComponent.new(name: "display") do |radio_group| %>
31
+ <% radio_group.with_item(value: "default", label: "Default", checked: true) %>
32
+ <% radio_group.with_item(value: "comfortable", label: "Comfortable") %>
33
+ <% radio_group.with_item(value: "compact", label: "Compact") %>
34
+ <% end %>
35
+ </div>
36
+ <% end %>
37
+
38
+ <%= render Docs::ComponentPreviewComponent.new(
39
+ title: "With Labels",
40
+ preview_content: with_labels_html,
41
+ code: radio_group_with_label_code,
42
+ ai_command: radio_group_with_label_code
43
+ ) %>
44
+
45
+ <!-- With Description Example -->
46
+ <% with_description_html = capture do %>
47
+ <%= render M9sh::RadioGroupComponent.new(name: "plan") do |radio_group| %>
48
+ <% radio_group.with_item(
49
+ value: "free",
50
+ label: "Free",
51
+ description: "Perfect for personal use",
52
+ checked: true
53
+ ) %>
54
+ <% radio_group.with_item(
55
+ value: "pro",
56
+ label: "Pro",
57
+ description: "Best for professionals"
58
+ ) %>
59
+ <% radio_group.with_item(
60
+ value: "enterprise",
61
+ label: "Enterprise",
62
+ description: "For large organizations"
63
+ ) %>
64
+ <% end %>
65
+ <% end %>
66
+
67
+ <%= render Docs::ComponentPreviewComponent.new(
68
+ title: "With Description",
69
+ preview_content: with_description_html,
70
+ code: radio_group_with_description_code,
71
+ ai_command: radio_group_with_description_code
72
+ ) %>
73
+
74
+ <!-- Disabled State Example -->
75
+ <% disabled_html = capture do %>
76
+ <%= render M9sh::RadioGroupComponent.new(name: "disabled-group") do |radio_group| %>
77
+ <% radio_group.with_item(value: "option1", label: "Available option", checked: true) %>
78
+ <% radio_group.with_item(value: "option2", label: "Disabled option", disabled: true) %>
79
+ <% radio_group.with_item(value: "option3", label: "Another available option") %>
80
+ <% end %>
81
+ <% end %>
82
+
83
+ <%= render Docs::ComponentPreviewComponent.new(
84
+ title: "Disabled State",
85
+ preview_content: disabled_html,
86
+ code: radio_group_disabled_code,
87
+ ai_command: radio_group_disabled_code
88
+ ) %>
89
+
90
+ <!-- Horizontal Layout Example -->
91
+ <% horizontal_html = capture do %>
92
+ <%= render M9sh::RadioGroupComponent.new(name: "horizontal", class: "flex flex-row space-x-4") do |radio_group| %>
93
+ <% radio_group.with_item(value: "yes", label: "Yes", checked: true) %>
94
+ <% radio_group.with_item(value: "no", label: "No") %>
95
+ <% radio_group.with_item(value: "maybe", label: "Maybe") %>
96
+ <% end %>
97
+ <% end %>
98
+
99
+ <%= render Docs::ComponentPreviewComponent.new(
100
+ title: "Horizontal Layout",
101
+ preview_content: horizontal_html,
102
+ code: radio_group_horizontal_code,
103
+ ai_command: radio_group_horizontal_code
104
+ ) %>
105
+
106
+ <!-- Form Integration Example -->
107
+ <% form_html = capture do %>
108
+ <div class="border rounded-lg p-6 max-w-md">
109
+ <h3 class="text-lg font-semibold mb-4">Notification Settings</h3>
110
+ <div class="space-y-6">
111
+ <div class="space-y-3">
112
+ <div class="font-medium">Email frequency</div>
113
+ <%= render M9sh::RadioGroupComponent.new(name: "email_frequency") do |radio_group| %>
114
+ <% radio_group.with_item(
115
+ value: "realtime",
116
+ label: "Realtime",
117
+ description: "Get notified immediately",
118
+ checked: true
119
+ ) %>
120
+ <% radio_group.with_item(
121
+ value: "daily",
122
+ label: "Daily digest",
123
+ description: "Once per day at 9 AM"
124
+ ) %>
125
+ <% radio_group.with_item(
126
+ value: "weekly",
127
+ label: "Weekly digest",
128
+ description: "Every Monday morning"
129
+ ) %>
130
+ <% radio_group.with_item(
131
+ value: "never",
132
+ label: "Never",
133
+ description: "Disable email notifications"
134
+ ) %>
135
+ <% end %>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ <% end %>
140
+
141
+ <%= render Docs::ComponentPreviewComponent.new(
142
+ title: "Form Integration",
143
+ preview_content: form_html,
144
+ code: radio_group_form_integration_code,
145
+ ai_command: radio_group_form_integration_code
146
+ ) %>
147
+ <% end %>
148
+ <% page.with_api do %>
149
+ <h3 class="text-lg font-semibold">RadioGroupComponent</h3>
150
+ <%= render Docs::PropTableComponent.new(
151
+ props: [
152
+ {
153
+ name: "name",
154
+ type: "String",
155
+ default: "nil",
156
+ description: "The name attribute for the radio group (required for form submissions)"
157
+ }
158
+ ]
159
+ ) %>
160
+
161
+ <h3 class="text-lg font-semibold mt-6">ItemComponent (via with_item)</h3>
162
+ <%= render Docs::PropTableComponent.new(
163
+ props: [
164
+ {
165
+ name: "value",
166
+ type: "String",
167
+ default: "nil",
168
+ description: "The value for this radio button (required)"
169
+ },
170
+ {
171
+ name: "label",
172
+ type: "String",
173
+ default: "nil",
174
+ description: "The label text for this radio button (required)"
175
+ },
176
+ {
177
+ name: "description",
178
+ type: "String",
179
+ default: "nil",
180
+ description: "Optional description text shown below the label"
181
+ },
182
+ {
183
+ name: "checked",
184
+ type: "Boolean",
185
+ default: "false",
186
+ description: "Whether this radio button is checked by default"
187
+ },
188
+ {
189
+ name: "disabled",
190
+ type: "Boolean",
191
+ default: "false",
192
+ description: "Whether this radio button is disabled"
193
+ }
194
+ ]
195
+ ) %>
196
+
197
+ <div class="mt-6 space-y-2">
198
+ <h3 class="text-lg font-semibold">Additional Notes</h3>
199
+ <ul class="list-disc list-inside space-y-2 text-sm text-muted-foreground">
200
+ <li>The radio group component uses a Stimulus controller (m9sh--radio) for state management</li>
201
+ <li>Only one radio button in a group can be selected at a time</li>
202
+ <li>The component automatically manages ARIA attributes for accessibility</li>
203
+ <li>By default, radio buttons are displayed vertically. Use the class parameter with flex utilities for horizontal layout</li>
204
+ <li>All additional HTML attributes can be passed via extra_attrs and will be applied to the container element</li>
205
+ <li>The radio group follows the WAI-ARIA design pattern for radio groups</li>
206
+ </ul>
207
+ </div>
208
+ <% end %>
209
+ <% end %>