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,227 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Toaster - m9sh Components") do |page| %>
2
+ <% page.with_header(
3
+ name: "Toaster",
4
+ description: "A container component that manages and displays toast notifications dynamically."
5
+ ) %>
6
+
7
+ <% page.with_installation(component_name: "toaster") %>
8
+
9
+ <% page.with_usage do %>
10
+ <p class="text-sm text-muted-foreground">
11
+ Add the Toaster component to your application layout (typically in <code class="text-xs bg-muted px-1 py-0.5 rounded">app/views/layouts/application.html.erb</code>):
12
+ </p>
13
+
14
+ <%= render Docs::CodeBlockComponent.new(
15
+ code: toaster_usage_code,
16
+ language: "erb"
17
+ ) %>
18
+
19
+ <p class="text-sm text-muted-foreground mt-4">
20
+ Then trigger toasts from anywhere in your JavaScript code using custom events:
21
+ </p>
22
+
23
+ <%= render Docs::CodeBlockComponent.new(
24
+ code: toaster_javascript_code,
25
+ language: "javascript"
26
+ ) %>
27
+ <% end %>
28
+
29
+ <% page.with_examples do %>
30
+ <!-- Default Setup Example -->
31
+ <% default_setup_html = capture do %>
32
+ <div class="space-y-4">
33
+ <p class="text-sm text-muted-foreground">
34
+ Add the Toaster component to your layout file:
35
+ </p>
36
+ <%= render Docs::CodeBlockComponent.new(
37
+ code: toaster_usage_code,
38
+ language: "erb"
39
+ ) %>
40
+ </div>
41
+ <% end %>
42
+
43
+ <%= render Docs::ComponentPreviewComponent.new(
44
+ title: "Default Setup",
45
+ preview_content: default_setup_html,
46
+ code: nil,
47
+ ai_command: nil
48
+ ) %>
49
+
50
+ <!-- Triggering Toasts Example -->
51
+ <% trigger_toasts_html = capture do %>
52
+ <div class="space-y-4">
53
+ <%= render M9sh::ButtonComponent.new(
54
+ onclick: "window.dispatchEvent(new CustomEvent('hotcdn:toast', { detail: { title: 'Success', description: 'Operation completed successfully.', variant: 'success' } }))"
55
+ ) do %>
56
+ Show Success Toast
57
+ <% end %>
58
+
59
+ <%= render M9sh::ButtonComponent.new(
60
+ variant: :destructive,
61
+ onclick: "window.dispatchEvent(new CustomEvent('hotcdn:toast', { detail: { title: 'Error', description: 'Something went wrong.', variant: 'error' } }))"
62
+ ) do %>
63
+ Show Error Toast
64
+ <% end %>
65
+
66
+ <%= render M9sh::ButtonComponent.new(
67
+ variant: :outline,
68
+ onclick: "window.dispatchEvent(new CustomEvent('hotcdn:toast', { detail: { title: 'Warning', description: 'Please review your changes.', variant: 'warning' } }))"
69
+ ) do %>
70
+ Show Warning Toast
71
+ <% end %>
72
+
73
+ <%= render M9sh::ButtonComponent.new(
74
+ variant: :secondary,
75
+ onclick: "window.dispatchEvent(new CustomEvent('hotcdn:toast', { detail: { title: 'Information', description: 'Here is some useful information.', variant: 'info' } }))"
76
+ ) do %>
77
+ Show Info Toast
78
+ <% end %>
79
+ </div>
80
+ <% end %>
81
+
82
+ <%= render Docs::ComponentPreviewComponent.new(
83
+ title: "Triggering Toasts",
84
+ preview_content: trigger_toasts_html,
85
+ code: toaster_example_code,
86
+ ai_command: toaster_example_code
87
+ ) %>
88
+
89
+ <!-- JavaScript Integration Example -->
90
+ <% js_integration_html = capture do %>
91
+ <div class="space-y-4">
92
+ <h4 class="text-sm font-semibold">From Stimulus Controllers</h4>
93
+ <%= render Docs::CodeBlockComponent.new(
94
+ code: <<~JAVASCRIPT,
95
+ // In your Stimulus controller
96
+ export default class extends Controller {
97
+ showToast() {
98
+ window.dispatchEvent(new CustomEvent('hotcdn:toast', {
99
+ detail: {
100
+ title: 'Success',
101
+ description: 'Your changes have been saved.',
102
+ variant: 'success',
103
+ duration: 5000
104
+ }
105
+ }));
106
+ }
107
+ }
108
+ JAVASCRIPT
109
+ language: "javascript"
110
+ ) %>
111
+
112
+ <h4 class="text-sm font-semibold mt-4">From Server Responses (Turbo Streams)</h4>
113
+ <%= render Docs::CodeBlockComponent.new(
114
+ code: toaster_turbo_stream_code,
115
+ language: "erb"
116
+ ) %>
117
+ </div>
118
+ <% end %>
119
+
120
+ <%= render Docs::ComponentPreviewComponent.new(
121
+ title: "JavaScript Integration",
122
+ preview_content: js_integration_html,
123
+ code: nil,
124
+ ai_command: nil
125
+ ) %>
126
+
127
+ <!-- Configuration Options Example -->
128
+ <% config_options_html = capture do %>
129
+ <div class="space-y-4">
130
+ <p class="text-sm text-muted-foreground">
131
+ The toaster listens for the <code class="text-xs bg-muted px-1 py-0.5 rounded">hotcdn:toast</code> custom event with the following options:
132
+ </p>
133
+
134
+ <%= render Docs::PropTableComponent.new(
135
+ props: [
136
+ {
137
+ name: "title",
138
+ type: "String",
139
+ default: nil,
140
+ description: "The title of the toast (required)"
141
+ },
142
+ {
143
+ name: "description",
144
+ type: "String",
145
+ default: "null",
146
+ description: "Optional description text"
147
+ },
148
+ {
149
+ name: "variant",
150
+ type: "String",
151
+ default: "'default'",
152
+ description: "Visual variant: 'default', 'success', 'error', 'warning', 'info'"
153
+ },
154
+ {
155
+ name: "duration",
156
+ type: "Number",
157
+ default: "5000",
158
+ description: "Duration in milliseconds before auto-dismiss"
159
+ }
160
+ ]
161
+ ) %>
162
+ </div>
163
+ <% end %>
164
+
165
+ <%= render Docs::ComponentPreviewComponent.new(
166
+ title: "Configuration Options",
167
+ preview_content: config_options_html,
168
+ code: nil,
169
+ ai_command: nil
170
+ ) %>
171
+ <% end %>
172
+
173
+ <% page.with_api do %>
174
+ <h3 class="text-lg font-semibold">Toaster Component</h3>
175
+ <p class="text-sm text-muted-foreground">
176
+ The Toaster component is a container that manages toast notifications. It doesn't accept any props and should be added once to your layout.
177
+ </p>
178
+
179
+ <div class="mt-6 space-y-2">
180
+ <h3 class="text-lg font-semibold">Event API</h3>
181
+ <p class="text-sm text-muted-foreground mb-4">
182
+ Toasts are triggered by dispatching a custom event on the window object:
183
+ </p>
184
+
185
+ <%= render Docs::CodeBlockComponent.new(
186
+ code: <<~JAVASCRIPT,
187
+ window.dispatchEvent(new CustomEvent('hotcdn:toast', {
188
+ detail: {
189
+ title: 'Title', // Required: The toast title
190
+ description: 'Message', // Optional: The toast description
191
+ variant: 'success', // Optional: Visual variant (default, success, error, warning, info)
192
+ duration: 5000 // Optional: Auto-dismiss duration in ms (default: 5000)
193
+ }
194
+ }));
195
+ JAVASCRIPT
196
+ language: "javascript"
197
+ ) %>
198
+ </div>
199
+
200
+ <div class="mt-6 space-y-2">
201
+ <h3 class="text-lg font-semibold">Notes</h3>
202
+ <ul class="list-disc list-inside text-sm text-muted-foreground space-y-1">
203
+ <li>The Toaster should be added to your application layout once, typically near the end of the body tag</li>
204
+ <li>Multiple toasts can be displayed simultaneously and are stacked vertically</li>
205
+ <li>Toasts are positioned at the bottom-right on mobile and can be configured via CSS classes</li>
206
+ <li>Each toast automatically dismisses after its duration expires (default 5 seconds)</li>
207
+ <li>Users can manually dismiss toasts by clicking the close button</li>
208
+ <li>The component uses Stimulus controllers to manage toast creation and removal</li>
209
+ <li>Toasts include smooth slide-in and slide-out animations</li>
210
+ <li>The container is accessible with proper ARIA live regions for screen readers</li>
211
+ </ul>
212
+ </div>
213
+
214
+ <div class="mt-6 space-y-2">
215
+ <h3 class="text-lg font-semibold">Best Practices</h3>
216
+ <ul class="list-disc list-inside text-sm text-muted-foreground space-y-1">
217
+ <li>Use success toasts for confirmations of user actions</li>
218
+ <li>Use error toasts for operations that failed and need user attention</li>
219
+ <li>Use warning toasts for important information that doesn't block the user</li>
220
+ <li>Use info toasts for general informational messages</li>
221
+ <li>Keep toast messages concise - ideally under two lines</li>
222
+ <li>Avoid showing too many toasts at once to prevent overwhelming the user</li>
223
+ <li>For critical errors that require action, consider using a modal or alert dialog instead</li>
224
+ </ul>
225
+ </div>
226
+ <% end %>
227
+ <% end %>
@@ -0,0 +1,154 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Toggle") do |page| %>
2
+ <% page.with_header(
3
+ name: "Toggle",
4
+ description: "A two-state button that can be either on or off."
5
+ ) %>
6
+
7
+ <% page.with_installation(component_name: "toggle") %>
8
+
9
+ <% page.with_usage do %>
10
+ <%= render Docs::CodeBlockComponent.new(
11
+ code: toggle_usage_code,
12
+ language: "erb"
13
+ ) %>
14
+ <% end %>
15
+
16
+ <% page.with_examples do %>
17
+ <!-- Default Example -->
18
+ <% toggle = M9sh::ToggleComponent.new %>
19
+ <% toggle_html = capture do %>
20
+ <%= render toggle do %>
21
+ Toggle
22
+ <% end %>
23
+ <% end %>
24
+
25
+ <%= render Docs::ComponentPreviewComponent.new(
26
+ title: "Default",
27
+ preview_content: toggle_html,
28
+ code: toggle_default_code,
29
+ ai_command: toggle_default_code
30
+ ) %>
31
+
32
+ <!-- With Text Example -->
33
+ <% with_text_html = capture do %>
34
+ <%= render M9sh::ToggleComponent.new do %>
35
+ Toggle with text
36
+ <% end %>
37
+ <% end %>
38
+
39
+ <%= render Docs::ComponentPreviewComponent.new(
40
+ title: "With Text",
41
+ preview_content: with_text_html,
42
+ code: toggle_with_text_code,
43
+ ai_command: toggle_with_text_code
44
+ ) %>
45
+
46
+ <!-- With Icon Example -->
47
+ <% with_icon_html = capture do %>
48
+ <%= render M9sh::ToggleComponent.new do %>
49
+ <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">
50
+ <path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>
51
+ <path d="m15 5 4 4"/>
52
+ </svg>
53
+ <% end %>
54
+ <% end %>
55
+
56
+ <%= render Docs::ComponentPreviewComponent.new(
57
+ title: "With Icon",
58
+ preview_content: with_icon_html,
59
+ code: toggle_with_icon_code,
60
+ ai_command: toggle_with_icon_code
61
+ ) %>
62
+
63
+ <!-- Disabled Example -->
64
+ <% disabled_html = capture do %>
65
+ <div class="flex gap-2">
66
+ <%= render M9sh::ToggleComponent.new(disabled: true) do %>
67
+ Disabled
68
+ <% end %>
69
+ <%= render M9sh::ToggleComponent.new(pressed: true, disabled: true) do %>
70
+ Disabled Pressed
71
+ <% end %>
72
+ </div>
73
+ <% end %>
74
+
75
+ <%= render Docs::ComponentPreviewComponent.new(
76
+ title: "Disabled",
77
+ preview_content: disabled_html,
78
+ code: toggle_disabled_code,
79
+ ai_command: toggle_disabled_code
80
+ ) %>
81
+
82
+ <!-- Variants Example -->
83
+ <% variants_html = capture do %>
84
+ <div class="flex gap-2">
85
+ <%= render M9sh::ToggleComponent.new(variant: :default) do %>
86
+ Default
87
+ <% end %>
88
+ <%= render M9sh::ToggleComponent.new(variant: :outline) do %>
89
+ Outline
90
+ <% end %>
91
+ </div>
92
+ <% end %>
93
+
94
+ <%= render Docs::ComponentPreviewComponent.new(
95
+ title: "Variants",
96
+ preview_content: variants_html,
97
+ code: toggle_variants_code,
98
+ ai_command: toggle_variants_code
99
+ ) %>
100
+
101
+ <!-- Sizes Example -->
102
+ <% sizes_html = capture do %>
103
+ <div class="flex items-center gap-2">
104
+ <%= render M9sh::ToggleComponent.new(size: :sm) do %>
105
+ Small
106
+ <% end %>
107
+ <%= render M9sh::ToggleComponent.new(size: :default) do %>
108
+ Default
109
+ <% end %>
110
+ <%= render M9sh::ToggleComponent.new(size: :lg) do %>
111
+ Large
112
+ <% end %>
113
+ </div>
114
+ <% end %>
115
+
116
+ <%= render Docs::ComponentPreviewComponent.new(
117
+ title: "Sizes",
118
+ preview_content: sizes_html,
119
+ code: toggle_sizes_code,
120
+ ai_command: toggle_sizes_code
121
+ ) %>
122
+ <% end %>
123
+
124
+ <% page.with_api do %>
125
+ <%= render Docs::PropTableComponent.new(
126
+ props: [
127
+ {
128
+ name: "variant",
129
+ type: "Symbol",
130
+ default: ":default",
131
+ description: "The visual style variant. Options: :default, :outline"
132
+ },
133
+ {
134
+ name: "size",
135
+ type: "Symbol",
136
+ default: ":default",
137
+ description: "The size of the toggle. Options: :sm, :default, :lg"
138
+ },
139
+ {
140
+ name: "pressed",
141
+ type: "Boolean",
142
+ default: "false",
143
+ description: "Whether the toggle is in the pressed (on) state"
144
+ },
145
+ {
146
+ name: "disabled",
147
+ type: "Boolean",
148
+ default: "false",
149
+ description: "Whether the toggle is disabled"
150
+ }
151
+ ]
152
+ ) %>
153
+ <% end %>
154
+ <% end %>
@@ -0,0 +1,216 @@
1
+ <%= render Docs::ComponentPageComponent.new(title: "Tooltip") do |page| %>
2
+ <% page.with_header(
3
+ name: "Tooltip",
4
+ description: "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it."
5
+ ) %>
6
+
7
+ <% page.with_installation(component_name: "tooltip") %>
8
+
9
+ <% page.with_usage do %>
10
+ <%= render Docs::CodeBlockComponent.new(
11
+ code: tooltip_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="flex justify-center items-center p-8">
20
+ <%= render M9sh::TooltipComponent.new do |tooltip| %>
21
+ <% tooltip.with_trigger do %>
22
+ <button class="px-4 py-2 rounded-md border bg-background hover:bg-muted transition-colors">
23
+ Hover me
24
+ </button>
25
+ <% end %>
26
+ <% tooltip.with_tooltip_content do %>
27
+ This is a tooltip
28
+ <% end %>
29
+ <% end %>
30
+ </div>
31
+ <% end %>
32
+
33
+ <%= render Docs::ComponentPreviewComponent.new(
34
+ title: "Default",
35
+ preview_content: default_html,
36
+ code: tooltip_default_code,
37
+ ai_command: tooltip_default_code
38
+ ) %>
39
+
40
+ <!-- Different Positions Example -->
41
+ <% positions_html = capture do %>
42
+ <div class="flex justify-center items-center gap-8 p-16">
43
+ <%= render M9sh::TooltipComponent.new(side: "top") do |tooltip| %>
44
+ <% tooltip.with_trigger do %>
45
+ <button class="px-4 py-2 rounded-md border bg-background hover:bg-muted transition-colors">
46
+ Top
47
+ </button>
48
+ <% end %>
49
+ <% tooltip.with_tooltip_content do %>
50
+ Tooltip on top
51
+ <% end %>
52
+ <% end %>
53
+
54
+ <%= render M9sh::TooltipComponent.new(side: "right") do |tooltip| %>
55
+ <% tooltip.with_trigger do %>
56
+ <button class="px-4 py-2 rounded-md border bg-background hover:bg-muted transition-colors">
57
+ Right
58
+ </button>
59
+ <% end %>
60
+ <% tooltip.with_tooltip_content do %>
61
+ Tooltip on right
62
+ <% end %>
63
+ <% end %>
64
+
65
+ <%= render M9sh::TooltipComponent.new(side: "bottom") do |tooltip| %>
66
+ <% tooltip.with_trigger do %>
67
+ <button class="px-4 py-2 rounded-md border bg-background hover:bg-muted transition-colors">
68
+ Bottom
69
+ </button>
70
+ <% end %>
71
+ <% tooltip.with_tooltip_content do %>
72
+ Tooltip on bottom
73
+ <% end %>
74
+ <% end %>
75
+
76
+ <%= render M9sh::TooltipComponent.new(side: "left") do |tooltip| %>
77
+ <% tooltip.with_trigger do %>
78
+ <button class="px-4 py-2 rounded-md border bg-background hover:bg-muted transition-colors">
79
+ Left
80
+ </button>
81
+ <% end %>
82
+ <% tooltip.with_tooltip_content do %>
83
+ Tooltip on left
84
+ <% end %>
85
+ <% end %>
86
+ </div>
87
+ <% end %>
88
+
89
+ <%= render Docs::ComponentPreviewComponent.new(
90
+ title: "Different Positions",
91
+ preview_content: positions_html,
92
+ code: tooltip_positions_code,
93
+ ai_command: tooltip_positions_code
94
+ ) %>
95
+
96
+ <!-- Rich Content Example -->
97
+ <% rich_content_html = capture do %>
98
+ <div class="flex justify-center items-center p-8">
99
+ <%= render M9sh::TooltipComponent.new do |tooltip| %>
100
+ <% tooltip.with_trigger do %>
101
+ <button class="px-4 py-2 rounded-md border bg-background hover:bg-muted transition-colors">
102
+ Hover for info
103
+ </button>
104
+ <% end %>
105
+ <% tooltip.with_tooltip_content do %>
106
+ <div class="space-y-1">
107
+ <p class="font-semibold">Pro Tip</p>
108
+ <p class="text-xs">You can use rich content in tooltips</p>
109
+ </div>
110
+ <% end %>
111
+ <% end %>
112
+ </div>
113
+ <% end %>
114
+
115
+ <%= render Docs::ComponentPreviewComponent.new(
116
+ title: "With Rich Content",
117
+ preview_content: rich_content_html,
118
+ code: tooltip_rich_content_code,
119
+ ai_command: tooltip_rich_content_code
120
+ ) %>
121
+ <% end %>
122
+
123
+ <% page.with_api do %>
124
+ <h3 class="text-lg font-semibold">TooltipComponent</h3>
125
+
126
+ <%= render Docs::PropTableComponent.new(
127
+ props: [
128
+ {
129
+ name: "side",
130
+ type: "String",
131
+ default: '"top"',
132
+ description: "The preferred side of the trigger to render against when open. Options: 'top', 'right', 'bottom', 'left'"
133
+ },
134
+ {
135
+ name: "delay",
136
+ type: "Number",
137
+ default: "200",
138
+ description: "The duration in milliseconds before the tooltip appears"
139
+ }
140
+ ]
141
+ ) %>
142
+
143
+ <h3 class="text-lg font-semibold mt-6">Slots</h3>
144
+
145
+ <%= render Docs::PropTableComponent.new(
146
+ props: [
147
+ {
148
+ name: "trigger",
149
+ type: "Slot",
150
+ default: "-",
151
+ description: "The element that triggers the tooltip on hover. This should be a focusable element."
152
+ },
153
+ {
154
+ name: "tooltip_content",
155
+ type: "Slot",
156
+ default: "-",
157
+ description: "The content to display in the tooltip"
158
+ }
159
+ ]
160
+ ) %>
161
+ <% end %>
162
+ <% end %>
163
+
164
+ <!-- Accessibility -->
165
+ <div class="space-y-4">
166
+ <h2 class="text-2xl font-semibold border-b pb-2">Accessibility</h2>
167
+
168
+ <div class="space-y-3 text-sm">
169
+ <p>
170
+ The Tooltip component uses Stimulus to manage visibility and positioning:
171
+ </p>
172
+
173
+ <ul class="list-disc list-inside space-y-2 text-muted-foreground">
174
+ <li>
175
+ The tooltip appears after a configurable delay when hovering over the trigger element
176
+ </li>
177
+ <li>
178
+ The tooltip uses the <code class="text-xs bg-muted px-1 py-0.5 rounded">role="tooltip"</code> attribute for proper screen reader support
179
+ </li>
180
+ <li>
181
+ Tooltips should provide supplementary information, not critical content that can't be accessed otherwise
182
+ </li>
183
+ <li>
184
+ The tooltip is positioned dynamically based on the <code class="text-xs bg-muted px-1 py-0.5 rounded">side</code> parameter
185
+ </li>
186
+ <li>
187
+ Smooth animations provide visual feedback when showing and hiding
188
+ </li>
189
+ </ul>
190
+ </div>
191
+ </div>
192
+
193
+ <!-- Notes -->
194
+ <div class="space-y-4">
195
+ <h2 class="text-2xl font-semibold border-b pb-2">Notes</h2>
196
+
197
+ <div class="space-y-3 text-sm">
198
+ <ul class="list-disc list-inside space-y-2 text-muted-foreground">
199
+ <li>
200
+ Tooltips are triggered on <code class="text-xs bg-muted px-1 py-0.5 rounded">mouseenter</code> and hidden on <code class="text-xs bg-muted px-1 py-0.5 rounded">mouseleave</code>
201
+ </li>
202
+ <li>
203
+ Use the <code class="text-xs bg-muted px-1 py-0.5 rounded">delay</code> parameter to control how quickly the tooltip appears
204
+ </li>
205
+ <li>
206
+ Tooltips are best used for short, informative text. For longer content, consider using a HoverCard or Popover instead
207
+ </li>
208
+ <li>
209
+ The tooltip content is styled with a dark background and light text by default
210
+ </li>
211
+ <li>
212
+ Multiple tooltips can exist on the same page independently
213
+ </li>
214
+ </ul>
215
+ </div>
216
+ </div>