m9sh 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.idea/hotcdn.iml +30 -0
- data/.mise.toml +2 -2
- data/app/assets/config/manifest.js +4 -0
- data/app/assets/images/icons/activity.svg +3 -0
- data/app/assets/images/icons/bell.svg +4 -0
- data/app/assets/images/icons/book.svg +4 -0
- data/app/assets/images/icons/chevron-down.svg +3 -0
- data/app/assets/images/icons/chevron-left.svg +3 -0
- data/app/assets/images/icons/chevron-right.svg +3 -0
- data/app/assets/images/icons/credit-card.svg +4 -0
- data/app/assets/images/icons/dollar-sign.svg +3 -0
- data/app/assets/images/icons/edit.svg +4 -0
- data/app/assets/images/icons/github.svg +3 -0
- data/app/assets/images/icons/home.svg +4 -0
- data/app/assets/images/icons/info.svg +5 -0
- data/app/assets/images/icons/layout.svg +6 -0
- data/app/assets/images/icons/logout.svg +5 -0
- data/app/assets/images/icons/menu.svg +5 -0
- data/app/assets/images/icons/moon.svg +3 -0
- data/app/assets/images/icons/paintbrush.svg +6 -0
- data/app/assets/images/icons/search.svg +4 -0
- data/app/assets/images/icons/settings.svg +4 -0
- data/app/assets/images/icons/sun.svg +11 -0
- data/app/assets/images/icons/user.svg +4 -0
- data/app/assets/images/icons/users.svg +5 -0
- data/app/assets/stylesheets/tailwind.css +1180 -0
- data/app/components/backdrop_component.rb +103 -0
- data/app/components/docs/code_block_component.rb +56 -0
- data/app/components/docs/component_api_component.rb +16 -0
- data/app/components/docs/component_examples_component.rb +16 -0
- data/app/components/docs/component_header_component.html.erb +8 -0
- data/app/components/docs/component_header_component.rb +14 -0
- data/app/components/docs/component_installation_component.html.erb +15 -0
- data/app/components/docs/component_installation_component.rb +13 -0
- data/app/components/docs/component_page_component.html.erb +9 -0
- data/app/components/docs/component_page_component.rb +19 -0
- data/app/components/docs/component_preview_component.rb +318 -0
- data/app/components/docs/component_usage_component.rb +18 -0
- data/app/components/docs/prop_table_component.rb +64 -0
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/blocks_controller.rb +51 -0
- data/app/controllers/docs_controller.rb +162 -0
- data/app/controllers/showcase_controller.rb +42 -0
- data/app/helpers/blocks_helper.rb +343 -0
- data/app/helpers/docs_helper.rb +3807 -0
- data/app/helpers/m9sh/toast_helper.rb +46 -0
- data/app/helpers/m9sh_helper.rb +343 -0
- data/app/javascript/application.js +3 -0
- data/app/javascript/controllers/application.js +9 -0
- data/app/javascript/controllers/backdrop_controller.js +137 -0
- data/app/javascript/controllers/color_customizer_controller.js +569 -0
- data/app/javascript/controllers/color_theme_controller.js +120 -0
- data/app/javascript/controllers/docs/component_preview_controller.js +149 -0
- data/app/javascript/controllers/docs/copy_button_controller.js +20 -0
- data/app/javascript/controllers/index.js +6 -0
- data/app/javascript/controllers/theme_controller.js +23 -0
- data/app/views/blocks/_sidebar.html.erb +31 -0
- data/app/views/blocks/_toc.html.erb +29 -0
- data/app/views/blocks/examples/dashboard-01.html.erb +180 -0
- data/app/views/blocks/examples/dashboard-02.html.erb +190 -0
- data/app/views/blocks/examples/dashboard-03.html.erb +210 -0
- data/app/views/blocks/examples/settings-01.html.erb +220 -0
- data/app/views/blocks/examples/settings-02.html.erb +231 -0
- data/app/views/blocks/examples/settings-03.html.erb +340 -0
- data/app/views/blocks/index.html.erb +65 -0
- data/app/views/docs/_sidebar.html.erb +47 -0
- data/app/views/docs/_toc.html.erb +19 -0
- data/app/views/docs/about.html.erb +68 -0
- data/app/views/docs/components/accordion.html.erb +196 -0
- data/app/views/docs/components/alert.html.erb +272 -0
- data/app/views/docs/components/alert_dialog.html.erb +232 -0
- data/app/views/docs/components/avatar.html.erb +207 -0
- data/app/views/docs/components/badge.html.erb +145 -0
- data/app/views/docs/components/breadcrumb.html.erb +264 -0
- data/app/views/docs/components/button.html.erb +229 -0
- data/app/views/docs/components/card.html.erb +378 -0
- data/app/views/docs/components/checkbox.html.erb +212 -0
- data/app/views/docs/components/collapsible.html.erb +252 -0
- data/app/views/docs/components/dialog.html.erb +323 -0
- data/app/views/docs/components/dropdown_menu.html.erb +289 -0
- data/app/views/docs/components/hover_card.html.erb +220 -0
- data/app/views/docs/components/input.html.erb +254 -0
- data/app/views/docs/components/label.html.erb +128 -0
- data/app/views/docs/components/main.html.erb +352 -0
- data/app/views/docs/components/navbar.html.erb +394 -0
- data/app/views/docs/components/navigation_menu.html.erb +226 -0
- data/app/views/docs/components/popover.html.erb +267 -0
- data/app/views/docs/components/progress.html.erb +107 -0
- data/app/views/docs/components/radio_group.html.erb +209 -0
- data/app/views/docs/components/select.html.erb +260 -0
- data/app/views/docs/components/separator.html.erb +162 -0
- data/app/views/docs/components/sheet.html.erb +270 -0
- data/app/views/docs/components/sidebar.html.erb +597 -0
- data/app/views/docs/components/skeleton.html.erb +150 -0
- data/app/views/docs/components/slider.html.erb +218 -0
- data/app/views/docs/components/spinner.html.erb +132 -0
- data/app/views/docs/components/switch.html.erb +148 -0
- data/app/views/docs/components/table.html.erb +259 -0
- data/app/views/docs/components/tabs.html.erb +225 -0
- data/app/views/docs/components/textarea.html.erb +239 -0
- data/app/views/docs/components/theme_toggle.html.erb +135 -0
- data/app/views/docs/components/toast.html.erb +205 -0
- data/app/views/docs/components/toaster.html.erb +227 -0
- data/app/views/docs/components/toggle.html.erb +154 -0
- data/app/views/docs/components/tooltip.html.erb +216 -0
- data/app/views/docs/components/typography.html.erb +180 -0
- data/app/views/docs/index.html.erb +143 -0
- data/app/views/docs/installation.html.erb +155 -0
- data/app/views/docs/simple_test.html.erb +13 -0
- data/app/views/docs/test_accordion.html.erb +14 -0
- data/app/views/docs/usage.html.erb +272 -0
- data/app/views/layouts/application.html.erb +107 -0
- data/app/views/layouts/backdrop.html.erb +77 -0
- data/app/views/shared/_app_navbar.html.erb +240 -0
- data/app/views/shared/_navbar.html.erb +69 -0
- data/app/views/showcase/v2/_components_grid.html.erb +38 -0
- data/app/views/showcase/v2/_features.html.erb +59 -0
- data/app/views/showcase/v2/_forms.html.erb +195 -0
- data/app/views/showcase/v2/_hero.html.erb +55 -0
- data/app/views/showcase/v2/_metrics.html.erb +107 -0
- data/app/views/showcase/v2.html.erb +18 -0
- data/lib/m9sh/version.rb +1 -1
- data/m9sh.gemspec +1 -1
- metadata +120 -1
@@ -0,0 +1,150 @@
|
|
1
|
+
<%= render Docs::ComponentPageComponent.new(title: "Skeleton") do |page| %>
|
2
|
+
<% page.with_header(
|
3
|
+
name: "Skeleton",
|
4
|
+
description: "Use to show a placeholder while content is loading."
|
5
|
+
) %>
|
6
|
+
|
7
|
+
<% page.with_installation(component_name: "skeleton") %>
|
8
|
+
|
9
|
+
<% page.with_usage do %>
|
10
|
+
<%= render Docs::CodeBlockComponent.new(
|
11
|
+
code: skeleton_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="space-y-2">
|
20
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-12") %>
|
21
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-12") %>
|
22
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-12") %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
27
|
+
title: "Default",
|
28
|
+
preview_content: default_html,
|
29
|
+
code: skeleton_default_code,
|
30
|
+
ai_command: skeleton_default_code
|
31
|
+
) %>
|
32
|
+
|
33
|
+
<!-- Rounded Example -->
|
34
|
+
<% rounded_html = capture do %>
|
35
|
+
<div class="flex flex-wrap items-center gap-4">
|
36
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :none, class: "w-20 h-12") %>
|
37
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :sm, class: "w-20 h-12") %>
|
38
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :md, class: "w-20 h-12") %>
|
39
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :lg, class: "w-20 h-12") %>
|
40
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :xl, class: "w-20 h-12") %>
|
41
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :full, class: "w-24 h-24") %>
|
42
|
+
</div>
|
43
|
+
<% end %>
|
44
|
+
|
45
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
46
|
+
title: "Rounded",
|
47
|
+
preview_content: rounded_html,
|
48
|
+
code: skeleton_rounded_code,
|
49
|
+
ai_command: skeleton_rounded_code
|
50
|
+
) %>
|
51
|
+
|
52
|
+
<!-- Card Skeleton Example -->
|
53
|
+
<% card_html = capture do %>
|
54
|
+
<div class="border rounded-lg p-6 space-y-4 max-w-md w-full">
|
55
|
+
<div class="flex items-center space-x-4">
|
56
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :full, class: "w-12 h-12") %>
|
57
|
+
<div class="flex-1 space-y-2">
|
58
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-3/4 h-4") %>
|
59
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-1/2 h-4") %>
|
60
|
+
</div>
|
61
|
+
</div>
|
62
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-32") %>
|
63
|
+
<div class="space-y-2">
|
64
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-4") %>
|
65
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-5/6 h-4") %>
|
66
|
+
</div>
|
67
|
+
</div>
|
68
|
+
<% end %>
|
69
|
+
|
70
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
71
|
+
title: "Card Skeleton",
|
72
|
+
preview_content: card_html,
|
73
|
+
code: skeleton_card_code,
|
74
|
+
ai_command: skeleton_card_code
|
75
|
+
) %>
|
76
|
+
|
77
|
+
<!-- List Skeleton Example -->
|
78
|
+
<% list_html = capture do %>
|
79
|
+
<div class="space-y-4 max-w-md w-full">
|
80
|
+
<% 3.times do %>
|
81
|
+
<div class="flex items-center space-x-4">
|
82
|
+
<%= render M9sh::SkeletonComponent.new(rounded: :full, class: "w-10 h-10") %>
|
83
|
+
<div class="flex-1 space-y-2">
|
84
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-3/4 h-4") %>
|
85
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-1/2 h-3") %>
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
<% end %>
|
89
|
+
</div>
|
90
|
+
<% end %>
|
91
|
+
|
92
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
93
|
+
title: "List Skeleton",
|
94
|
+
preview_content: list_html,
|
95
|
+
code: skeleton_list_code,
|
96
|
+
ai_command: skeleton_list_code
|
97
|
+
) %>
|
98
|
+
|
99
|
+
<!-- Table Skeleton Example -->
|
100
|
+
<% table_html = capture do %>
|
101
|
+
<div class="border rounded-lg overflow-hidden max-w-2xl w-full">
|
102
|
+
<div class="border-b p-4 space-y-2">
|
103
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-1/4 h-6") %>
|
104
|
+
</div>
|
105
|
+
<% 5.times do %>
|
106
|
+
<div class="border-b last:border-b-0 p-4">
|
107
|
+
<div class="grid grid-cols-3 gap-4">
|
108
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-4") %>
|
109
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-4") %>
|
110
|
+
<%= render M9sh::SkeletonComponent.new(class: "w-full h-4") %>
|
111
|
+
</div>
|
112
|
+
</div>
|
113
|
+
<% end %>
|
114
|
+
</div>
|
115
|
+
<% end %>
|
116
|
+
|
117
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
118
|
+
title: "Table Skeleton",
|
119
|
+
preview_content: table_html,
|
120
|
+
code: skeleton_table_code,
|
121
|
+
ai_command: skeleton_table_code
|
122
|
+
) %>
|
123
|
+
<% end %>
|
124
|
+
|
125
|
+
<% page.with_api do %>
|
126
|
+
<%= render Docs::PropTableComponent.new(
|
127
|
+
props: [
|
128
|
+
{
|
129
|
+
name: "rounded",
|
130
|
+
type: "Symbol",
|
131
|
+
default: ":md",
|
132
|
+
description: "The border radius. Options: :none, :sm, :md, :lg, :xl, :full"
|
133
|
+
},
|
134
|
+
{
|
135
|
+
name: "class",
|
136
|
+
type: "String",
|
137
|
+
default: nil,
|
138
|
+
description: "Additional CSS classes for custom sizing and styling"
|
139
|
+
}
|
140
|
+
]
|
141
|
+
) %>
|
142
|
+
|
143
|
+
<div class="mt-4 p-4 border rounded-lg bg-muted">
|
144
|
+
<p class="text-sm text-muted-foreground">
|
145
|
+
<strong>Note:</strong> The Skeleton component requires custom sizing via the <code class="text-sm">class</code> parameter.
|
146
|
+
Use Tailwind utilities like <code class="text-sm">w-full h-12</code> to define dimensions.
|
147
|
+
</p>
|
148
|
+
</div>
|
149
|
+
<% end %>
|
150
|
+
<% end %>
|
@@ -0,0 +1,218 @@
|
|
1
|
+
<%= render Docs::ComponentPageComponent.new(title: "Slider") do |page| %>
|
2
|
+
<% page.with_header(
|
3
|
+
name: "Slider",
|
4
|
+
description: "An input where the user selects a value from within a given range."
|
5
|
+
) %>
|
6
|
+
|
7
|
+
<% page.with_installation(component_name: "slider") %>
|
8
|
+
|
9
|
+
<% page.with_usage do %>
|
10
|
+
<%= render Docs::CodeBlockComponent.new(
|
11
|
+
code: slider_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::SliderComponent.new %>
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
23
|
+
title: "Default",
|
24
|
+
preview_content: default_html,
|
25
|
+
code: slider_usage_code,
|
26
|
+
ai_command: slider_usage_code
|
27
|
+
) %>
|
28
|
+
|
29
|
+
<!-- With Min/Max/Step Example -->
|
30
|
+
<% range_html = capture do %>
|
31
|
+
<div class="space-y-2 w-full">
|
32
|
+
<%= render M9sh::SliderComponent.new(
|
33
|
+
min: 0,
|
34
|
+
max: 100,
|
35
|
+
step: 5,
|
36
|
+
value: 50
|
37
|
+
) %>
|
38
|
+
<p class="text-sm text-muted-foreground">Range: 0-100, Step: 5</p>
|
39
|
+
</div>
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
43
|
+
title: "With Min/Max/Step",
|
44
|
+
preview_content: range_html,
|
45
|
+
code: slider_range_code,
|
46
|
+
ai_command: slider_range_code
|
47
|
+
) %>
|
48
|
+
|
49
|
+
<!-- With Labels Example -->
|
50
|
+
<% labels_html = capture do %>
|
51
|
+
<div class="space-y-4 w-full">
|
52
|
+
<div class="space-y-2">
|
53
|
+
<%= render M9sh::LabelComponent.new(for_id: "volume") do %>
|
54
|
+
Volume
|
55
|
+
<% end %>
|
56
|
+
<%= render M9sh::SliderComponent.new(
|
57
|
+
name: "volume",
|
58
|
+
min: 0,
|
59
|
+
max: 100,
|
60
|
+
value: 75
|
61
|
+
) %>
|
62
|
+
</div>
|
63
|
+
<div class="space-y-2">
|
64
|
+
<%= render M9sh::LabelComponent.new(for_id: "brightness") do %>
|
65
|
+
Brightness
|
66
|
+
<% end %>
|
67
|
+
<%= render M9sh::SliderComponent.new(
|
68
|
+
name: "brightness",
|
69
|
+
min: 0,
|
70
|
+
max: 100,
|
71
|
+
value: 50
|
72
|
+
) %>
|
73
|
+
</div>
|
74
|
+
</div>
|
75
|
+
<% end %>
|
76
|
+
|
77
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
78
|
+
title: "With Labels",
|
79
|
+
preview_content: labels_html,
|
80
|
+
code: slider_with_label_code,
|
81
|
+
ai_command: slider_with_label_code
|
82
|
+
) %>
|
83
|
+
|
84
|
+
<!-- Disabled State Example -->
|
85
|
+
<% disabled_html = capture do %>
|
86
|
+
<div class="space-y-4 w-full">
|
87
|
+
<%= render M9sh::SliderComponent.new(
|
88
|
+
value: 50,
|
89
|
+
disabled: true
|
90
|
+
) %>
|
91
|
+
<p class="text-sm text-muted-foreground">This slider is disabled</p>
|
92
|
+
</div>
|
93
|
+
<% end %>
|
94
|
+
|
95
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
96
|
+
title: "Disabled",
|
97
|
+
preview_content: disabled_html,
|
98
|
+
code: slider_disabled_code,
|
99
|
+
ai_command: slider_disabled_code
|
100
|
+
) %>
|
101
|
+
|
102
|
+
<!-- Form Integration Example -->
|
103
|
+
<% form_html = capture do %>
|
104
|
+
<div class="space-y-6 w-full max-w-md">
|
105
|
+
<div class="space-y-4">
|
106
|
+
<div class="space-y-2">
|
107
|
+
<div class="flex items-center justify-between">
|
108
|
+
<%= render M9sh::LabelComponent.new(for_id: "price-range") do %>
|
109
|
+
Price Range
|
110
|
+
<% end %>
|
111
|
+
<span class="text-sm text-muted-foreground" id="price-value">$50</span>
|
112
|
+
</div>
|
113
|
+
<%= render M9sh::SliderComponent.new(
|
114
|
+
name: "price",
|
115
|
+
min: 0,
|
116
|
+
max: 100,
|
117
|
+
step: 1,
|
118
|
+
value: 50
|
119
|
+
) %>
|
120
|
+
</div>
|
121
|
+
|
122
|
+
<div class="space-y-2">
|
123
|
+
<div class="flex items-center justify-between">
|
124
|
+
<%= render M9sh::LabelComponent.new(for_id: "quantity") do %>
|
125
|
+
Quantity
|
126
|
+
<% end %>
|
127
|
+
<span class="text-sm text-muted-foreground" id="quantity-value">5</span>
|
128
|
+
</div>
|
129
|
+
<%= render M9sh::SliderComponent.new(
|
130
|
+
name: "quantity",
|
131
|
+
min: 1,
|
132
|
+
max: 10,
|
133
|
+
step: 1,
|
134
|
+
value: 5
|
135
|
+
) %>
|
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: slider_form_integration_code,
|
145
|
+
ai_command: slider_form_integration_code
|
146
|
+
) %>
|
147
|
+
<% end %>
|
148
|
+
|
149
|
+
<% page.with_api do %>
|
150
|
+
<%= render Docs::PropTableComponent.new(
|
151
|
+
props: [
|
152
|
+
{
|
153
|
+
name: "min",
|
154
|
+
type: "Number",
|
155
|
+
default: "0",
|
156
|
+
description: "The minimum value of the slider."
|
157
|
+
},
|
158
|
+
{
|
159
|
+
name: "max",
|
160
|
+
type: "Number",
|
161
|
+
default: "100",
|
162
|
+
description: "The maximum value of the slider."
|
163
|
+
},
|
164
|
+
{
|
165
|
+
name: "step",
|
166
|
+
type: "Number",
|
167
|
+
default: "1",
|
168
|
+
description: "The stepping interval between values."
|
169
|
+
},
|
170
|
+
{
|
171
|
+
name: "value",
|
172
|
+
type: "Number",
|
173
|
+
default: "50",
|
174
|
+
description: "The current value of the slider."
|
175
|
+
},
|
176
|
+
{
|
177
|
+
name: "name",
|
178
|
+
type: "String",
|
179
|
+
default: "nil",
|
180
|
+
description: "The name attribute for the hidden input element. Used for form submissions."
|
181
|
+
},
|
182
|
+
{
|
183
|
+
name: "disabled",
|
184
|
+
type: "Boolean",
|
185
|
+
default: "false",
|
186
|
+
description: "Whether the slider is disabled and cannot be interacted with."
|
187
|
+
}
|
188
|
+
]
|
189
|
+
) %>
|
190
|
+
|
191
|
+
<div class="mt-6 space-y-2">
|
192
|
+
<h3 class="text-lg font-semibold">Accessibility</h3>
|
193
|
+
<ul class="list-disc list-inside space-y-1 text-sm text-muted-foreground">
|
194
|
+
<li>Uses proper ARIA attributes (<code>role="slider"</code>, <code>aria-valuenow</code>, <code>aria-valuemin</code>, <code>aria-valuemax</code>)</li>
|
195
|
+
<li>Keyboard accessible with arrow keys for precise control</li>
|
196
|
+
<li>Works with screen readers to announce value changes</li>
|
197
|
+
<li>Supports disabled state with proper cursor and opacity</li>
|
198
|
+
<li>Focus indicators for keyboard navigation</li>
|
199
|
+
</ul>
|
200
|
+
</div>
|
201
|
+
|
202
|
+
<div class="mt-6 space-y-2">
|
203
|
+
<h3 class="text-lg font-semibold">Stimulus Controller</h3>
|
204
|
+
<p class="text-sm text-muted-foreground">
|
205
|
+
The slider component uses a Stimulus controller (<code>m9sh--slider</code>) to handle dragging interactions.
|
206
|
+
The controller manages mouse and touch events, updates the visual position of the thumb and range indicators,
|
207
|
+
and maintains the current value in a hidden input field when a <code>name</code> is provided.
|
208
|
+
</p>
|
209
|
+
</div>
|
210
|
+
|
211
|
+
<div class="mt-4 p-4 border rounded-lg bg-muted">
|
212
|
+
<p class="text-sm text-muted-foreground">
|
213
|
+
<strong>Note:</strong> The slider value can be changed by clicking/tapping on the track, or by dragging the thumb.
|
214
|
+
When used in a form with a <code class="text-sm">name</code> attribute, the slider value will be submitted with the form data.
|
215
|
+
</p>
|
216
|
+
</div>
|
217
|
+
<% end %>
|
218
|
+
<% end %>
|
@@ -0,0 +1,132 @@
|
|
1
|
+
<%= render Docs::ComponentPageComponent.new(title: "Spinner") do |page| %>
|
2
|
+
<% page.with_header(
|
3
|
+
name: "Spinner",
|
4
|
+
description: "An indicator that can be used to show a loading state."
|
5
|
+
) %>
|
6
|
+
|
7
|
+
<% page.with_installation(component_name: "spinner") %>
|
8
|
+
|
9
|
+
<% page.with_usage do %>
|
10
|
+
<%= render Docs::CodeBlockComponent.new(
|
11
|
+
code: spinner_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 items-center justify-center py-12">
|
20
|
+
<%= render M9sh::SpinnerComponent.new %>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
25
|
+
title: "Default",
|
26
|
+
preview_content: default_html,
|
27
|
+
code: spinner_usage_code,
|
28
|
+
ai_command: spinner_usage_code
|
29
|
+
) %>
|
30
|
+
|
31
|
+
<!-- Sizes Example -->
|
32
|
+
<% sizes_html = capture do %>
|
33
|
+
<div class="flex items-center justify-center gap-4 py-12">
|
34
|
+
<%= render M9sh::SpinnerComponent.new(size: :sm) %>
|
35
|
+
<%= render M9sh::SpinnerComponent.new %>
|
36
|
+
<%= render M9sh::SpinnerComponent.new(size: :md) %>
|
37
|
+
<%= render M9sh::SpinnerComponent.new(size: :lg) %>
|
38
|
+
</div>
|
39
|
+
<% end %>
|
40
|
+
|
41
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
42
|
+
title: "Sizes",
|
43
|
+
preview_content: sizes_html,
|
44
|
+
code: spinner_sizes_code,
|
45
|
+
ai_command: spinner_sizes_code
|
46
|
+
) %>
|
47
|
+
|
48
|
+
<!-- Colors Example -->
|
49
|
+
<% colors_html = capture do %>
|
50
|
+
<div class="flex items-center justify-center gap-4 py-12">
|
51
|
+
<%= render M9sh::SpinnerComponent.new(class: "text-primary") %>
|
52
|
+
<%= render M9sh::SpinnerComponent.new(class: "text-destructive") %>
|
53
|
+
<%= render M9sh::SpinnerComponent.new(class: "text-green-500") %>
|
54
|
+
<%= render M9sh::SpinnerComponent.new(class: "text-blue-500") %>
|
55
|
+
</div>
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
59
|
+
title: "Colors",
|
60
|
+
preview_content: colors_html,
|
61
|
+
code: spinner_colors_code,
|
62
|
+
ai_command: spinner_colors_code
|
63
|
+
) %>
|
64
|
+
|
65
|
+
<!-- Button with Spinner Example -->
|
66
|
+
<% button_html = capture do %>
|
67
|
+
<div class="flex items-center justify-center gap-4 py-12">
|
68
|
+
<%= render M9sh::ButtonComponent.new(disabled: true) do %>
|
69
|
+
<div class="flex items-center gap-2">
|
70
|
+
<%= render M9sh::SpinnerComponent.new(size: :sm) %>
|
71
|
+
<span>Please wait</span>
|
72
|
+
</div>
|
73
|
+
<% end %>
|
74
|
+
</div>
|
75
|
+
<% end %>
|
76
|
+
|
77
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
78
|
+
title: "Button with Spinner",
|
79
|
+
preview_content: button_html,
|
80
|
+
code: spinner_button_code,
|
81
|
+
ai_command: spinner_button_code
|
82
|
+
) %>
|
83
|
+
<% end %>
|
84
|
+
|
85
|
+
<% page.with_api do %>
|
86
|
+
<%= render Docs::PropTableComponent.new(
|
87
|
+
props: [
|
88
|
+
{
|
89
|
+
name: "size",
|
90
|
+
type: "Symbol",
|
91
|
+
default: ":default",
|
92
|
+
description: "The size of the spinner. Options: :sm, :default, :md, :lg"
|
93
|
+
},
|
94
|
+
{
|
95
|
+
name: "class",
|
96
|
+
type: "String",
|
97
|
+
default: "nil",
|
98
|
+
description: "Additional CSS classes (e.g., for custom colors using text utilities)"
|
99
|
+
}
|
100
|
+
]
|
101
|
+
) %>
|
102
|
+
|
103
|
+
<div class="mt-6 space-y-3 text-sm">
|
104
|
+
<h3 class="text-lg font-semibold">Size Mapping</h3>
|
105
|
+
<ul class="list-disc list-inside space-y-1 text-muted-foreground">
|
106
|
+
<li><code class="text-xs bg-muted px-1 py-0.5 rounded">:sm</code> - size-3 (12px)</li>
|
107
|
+
<li><code class="text-xs bg-muted px-1 py-0.5 rounded">:default</code> - size-4 (16px)</li>
|
108
|
+
<li><code class="text-xs bg-muted px-1 py-0.5 rounded">:md</code> - size-6 (24px)</li>
|
109
|
+
<li><code class="text-xs bg-muted px-1 py-0.5 rounded">:lg</code> - size-8 (32px)</li>
|
110
|
+
</ul>
|
111
|
+
</div>
|
112
|
+
|
113
|
+
<div class="mt-6 space-y-3 text-sm">
|
114
|
+
<h3 class="text-lg font-semibold">Usage Notes</h3>
|
115
|
+
<ul class="list-disc list-inside space-y-2 text-muted-foreground">
|
116
|
+
<li>The spinner uses the <code class="text-xs bg-muted px-1 py-0.5 rounded">animate-spin</code> class for smooth rotation animation</li>
|
117
|
+
<li>Color is controlled by the current text color, making it easy to customize with Tailwind utilities</li>
|
118
|
+
<li>The spinner is an inline element and can be placed inside buttons, badges, or other components</li>
|
119
|
+
<li>Use smaller sizes (:sm) when placing inside buttons or compact UI elements</li>
|
120
|
+
</ul>
|
121
|
+
</div>
|
122
|
+
|
123
|
+
<div class="mt-6 space-y-3 text-sm">
|
124
|
+
<h3 class="text-lg font-semibold">Accessibility</h3>
|
125
|
+
<ul class="list-disc list-inside space-y-2 text-muted-foreground">
|
126
|
+
<li>The spinner has <code class="text-xs bg-muted px-1 py-0.5 rounded">role="status"</code> for screen readers</li>
|
127
|
+
<li>Includes <code class="text-xs bg-muted px-1 py-0.5 rounded">aria-label="Loading"</code> to provide context</li>
|
128
|
+
<li>Consider adding visually hidden text for more specific loading context when needed</li>
|
129
|
+
</ul>
|
130
|
+
</div>
|
131
|
+
<% end %>
|
132
|
+
<% end %>
|
@@ -0,0 +1,148 @@
|
|
1
|
+
<%= render Docs::ComponentPageComponent.new(title: "Switch") do |page| %>
|
2
|
+
<% page.with_header(
|
3
|
+
name: "Switch",
|
4
|
+
description: "A control that allows the user to toggle between checked and unchecked states."
|
5
|
+
) %>
|
6
|
+
|
7
|
+
<% page.with_installation(component_name: "switch") %>
|
8
|
+
|
9
|
+
<% page.with_usage do %>
|
10
|
+
<%= render Docs::CodeBlockComponent.new(
|
11
|
+
code: switch_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::SwitchComponent.new %>
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
23
|
+
title: "Default",
|
24
|
+
preview_content: default_html,
|
25
|
+
code: switch_usage_code,
|
26
|
+
ai_command: switch_usage_code
|
27
|
+
) %>
|
28
|
+
|
29
|
+
<!-- With Label Example -->
|
30
|
+
<% with_label_html = capture do %>
|
31
|
+
<div class="flex items-center space-x-2">
|
32
|
+
<%= render M9sh::SwitchComponent.new(name: "notifications") %>
|
33
|
+
<%= render M9sh::LabelComponent.new(for_id: "notifications") do %>
|
34
|
+
Enable notifications
|
35
|
+
<% end %>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
38
|
+
|
39
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
40
|
+
title: "With Label",
|
41
|
+
preview_content: with_label_html,
|
42
|
+
code: switch_with_label_code,
|
43
|
+
ai_command: switch_with_label_code
|
44
|
+
) %>
|
45
|
+
|
46
|
+
<!-- Checked State Example -->
|
47
|
+
<% checked_html = capture do %>
|
48
|
+
<%= render M9sh::SwitchComponent.new(checked: true) %>
|
49
|
+
<% end %>
|
50
|
+
|
51
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
52
|
+
title: "Checked State",
|
53
|
+
preview_content: checked_html,
|
54
|
+
code: switch_checked_code,
|
55
|
+
ai_command: switch_checked_code
|
56
|
+
) %>
|
57
|
+
|
58
|
+
<!-- Disabled State Example -->
|
59
|
+
<% disabled_html = capture do %>
|
60
|
+
<div class="flex flex-col space-y-4">
|
61
|
+
<%= render M9sh::SwitchComponent.new(disabled: true) %>
|
62
|
+
<%= render M9sh::SwitchComponent.new(checked: true, disabled: true) %>
|
63
|
+
</div>
|
64
|
+
<% end %>
|
65
|
+
|
66
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
67
|
+
title: "Disabled State",
|
68
|
+
preview_content: disabled_html,
|
69
|
+
code: switch_disabled_states_code,
|
70
|
+
ai_command: switch_disabled_states_code
|
71
|
+
) %>
|
72
|
+
|
73
|
+
<!-- Form Integration Example -->
|
74
|
+
<% form_html = capture do %>
|
75
|
+
<div class="space-y-4">
|
76
|
+
<div class="flex items-center justify-between">
|
77
|
+
<div class="space-y-0.5">
|
78
|
+
<div class="font-medium">Marketing emails</div>
|
79
|
+
<div class="text-sm text-muted-foreground">
|
80
|
+
Receive emails about new products and features.
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
<%= render M9sh::SwitchComponent.new(name: "marketing_emails", checked: true) %>
|
84
|
+
</div>
|
85
|
+
|
86
|
+
<div class="flex items-center justify-between">
|
87
|
+
<div class="space-y-0.5">
|
88
|
+
<div class="font-medium">Security emails</div>
|
89
|
+
<div class="text-sm text-muted-foreground">
|
90
|
+
Receive emails about your account security.
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
<%= render M9sh::SwitchComponent.new(name: "security_emails", checked: true, disabled: true) %>
|
94
|
+
</div>
|
95
|
+
</div>
|
96
|
+
<% end %>
|
97
|
+
|
98
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
99
|
+
title: "Form Integration",
|
100
|
+
preview_content: form_html,
|
101
|
+
code: switch_form_code,
|
102
|
+
ai_command: switch_form_code
|
103
|
+
) %>
|
104
|
+
<% end %>
|
105
|
+
|
106
|
+
<% page.with_api do %>
|
107
|
+
<%= render Docs::PropTableComponent.new(
|
108
|
+
props: [
|
109
|
+
{
|
110
|
+
name: "name",
|
111
|
+
type: "String",
|
112
|
+
default: "nil",
|
113
|
+
description: "The name attribute for the switch input element. Used for form submissions."
|
114
|
+
},
|
115
|
+
{
|
116
|
+
name: "checked",
|
117
|
+
type: "Boolean",
|
118
|
+
default: "false",
|
119
|
+
description: "Whether the switch is in the checked state."
|
120
|
+
},
|
121
|
+
{
|
122
|
+
name: "disabled",
|
123
|
+
type: "Boolean",
|
124
|
+
default: "false",
|
125
|
+
description: "Whether the switch is disabled and cannot be interacted with."
|
126
|
+
}
|
127
|
+
]
|
128
|
+
) %>
|
129
|
+
|
130
|
+
<div class="mt-6 space-y-2">
|
131
|
+
<h3 class="text-lg font-semibold">Accessibility</h3>
|
132
|
+
<ul class="list-disc list-inside space-y-1 text-sm text-muted-foreground">
|
133
|
+
<li>Uses proper ARIA attributes (<code>role="switch"</code> and <code>aria-checked</code>)</li>
|
134
|
+
<li>Keyboard accessible with focus indicators</li>
|
135
|
+
<li>Works with screen readers</li>
|
136
|
+
<li>Supports disabled state with proper cursor and opacity</li>
|
137
|
+
</ul>
|
138
|
+
</div>
|
139
|
+
|
140
|
+
<div class="mt-6 space-y-2">
|
141
|
+
<h3 class="text-lg font-semibold">Stimulus Controller</h3>
|
142
|
+
<p class="text-sm text-muted-foreground">
|
143
|
+
The switch component uses a Stimulus controller (<code>m9sh--switch</code>) to handle state toggling.
|
144
|
+
The controller manages the checked state and updates the visual appearance when clicked.
|
145
|
+
</p>
|
146
|
+
</div>
|
147
|
+
<% end %>
|
148
|
+
<% end %>
|