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,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BackdropComponent < ViewComponent::Base
|
4
|
+
renders_one :navbar
|
5
|
+
renders_one :left_sidebar
|
6
|
+
renders_one :right_sidebar
|
7
|
+
renders_one :main_content
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
show_left_sidebar: true,
|
11
|
+
show_right_sidebar: true,
|
12
|
+
left_sidebar_width: "w-[300px]",
|
13
|
+
right_sidebar_width: "w-[300px]",
|
14
|
+
**extra_attrs
|
15
|
+
)
|
16
|
+
@show_left_sidebar = show_left_sidebar
|
17
|
+
@show_right_sidebar = show_right_sidebar
|
18
|
+
@left_sidebar_width = left_sidebar_width
|
19
|
+
@right_sidebar_width = right_sidebar_width
|
20
|
+
@extra_attrs = extra_attrs
|
21
|
+
super()
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
tag.div(**component_attrs("h-screen w-screen flex flex-col overflow-hidden"), data: { controller: "backdrop" }) do
|
26
|
+
safe_join([
|
27
|
+
render_navbar_section,
|
28
|
+
render_overlay,
|
29
|
+
render_content_area
|
30
|
+
].compact)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def component_attrs(default_classes)
|
37
|
+
classes = [default_classes, @extra_attrs[:class]].compact.join(" ")
|
38
|
+
@extra_attrs.merge(class: classes)
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_navbar_section
|
42
|
+
return unless navbar?
|
43
|
+
|
44
|
+
# Render navbar with toggle buttons passed as slots
|
45
|
+
navbar.to_s.html_safe
|
46
|
+
end
|
47
|
+
|
48
|
+
def render_content_area
|
49
|
+
tag.div(class: "flex-1 flex overflow-hidden") do
|
50
|
+
safe_join([
|
51
|
+
render_left_sidebar_section,
|
52
|
+
render_main_content_section,
|
53
|
+
render_right_sidebar_section
|
54
|
+
].compact)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def render_left_sidebar_section
|
59
|
+
return unless @show_left_sidebar && left_sidebar?
|
60
|
+
|
61
|
+
# Convert width to responsive: w-[300px] -> w-screen md:w-[300px]
|
62
|
+
desktop_width = @left_sidebar_width.start_with?('w-') ? "md:#{@left_sidebar_width}" : "md:w-[300px]"
|
63
|
+
|
64
|
+
tag.div(
|
65
|
+
class: "pt-16 w-screen #{desktop_width} flex-shrink-0 overflow-y-auto fixed md:relative left-0 md:left-auto -translate-x-full md:translate-x-0 top-0 bottom-0 md:bottom-auto z-40 md:z-auto transition-all duration-300 ease-in-out bg-transparent md:bg-background shadow-xl md:shadow-none md:border-r md:border-transparent",
|
66
|
+
data: { backdrop_target: "leftSidebar" }
|
67
|
+
) do
|
68
|
+
left_sidebar.to_s.html_safe
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def render_main_content_section
|
73
|
+
return unless main_content?
|
74
|
+
|
75
|
+
tag.div(class: "pt-16 flex-1 overflow-auto") do
|
76
|
+
main_content.to_s.html_safe
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def render_right_sidebar_section
|
81
|
+
return unless @show_right_sidebar && right_sidebar?
|
82
|
+
|
83
|
+
# Convert width to responsive: w-[300px] -> w-screen md:w-[300px]
|
84
|
+
desktop_width = @right_sidebar_width.start_with?('w-') ? "md:#{@right_sidebar_width}" : "md:w-[300px]"
|
85
|
+
|
86
|
+
tag.div(
|
87
|
+
class: "pt-16 w-screen #{desktop_width} flex-shrink-0 overflow-y-auto fixed md:relative right-0 md:right-auto translate-x-full md:translate-x-0 top-0 bottom-0 md:bottom-auto z-40 md:z-auto transition-all duration-300 ease-in-out bg-transparent md:bg-background shadow-xl md:shadow-none md:border-l md:border-transparent",
|
88
|
+
data: { backdrop_target: "rightSidebar" }
|
89
|
+
) do
|
90
|
+
right_sidebar.to_s.html_safe
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def render_overlay
|
95
|
+
tag.div(
|
96
|
+
class: "hidden fixed top-16 left-0 right-0 bottom-0 bg-background/80 backdrop-blur-sm z-30 md:hidden",
|
97
|
+
data: {
|
98
|
+
backdrop_target: "overlay",
|
99
|
+
action: "click->backdrop#closeAll"
|
100
|
+
}
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rouge"
|
4
|
+
|
5
|
+
module Docs
|
6
|
+
class CodeBlockComponent < ViewComponent::Base
|
7
|
+
attr_reader :code, :language, :show_copy
|
8
|
+
|
9
|
+
def initialize(code:, language: "ruby", show_copy: true)
|
10
|
+
@code = code.strip
|
11
|
+
@language = language
|
12
|
+
@show_copy = show_copy
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
tag.div(class: "relative my-4") do
|
17
|
+
safe_join([
|
18
|
+
copy_button,
|
19
|
+
highlighted_code
|
20
|
+
])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def copy_button
|
27
|
+
return unless @show_copy
|
28
|
+
|
29
|
+
tag.div(class: "absolute right-2 top-2") do
|
30
|
+
render M9sh::ButtonComponent.new(
|
31
|
+
variant: :ghost,
|
32
|
+
size: :sm,
|
33
|
+
data: {
|
34
|
+
controller: "docs--copy-button",
|
35
|
+
action: "click->docs--copy-button#copy",
|
36
|
+
docs__copy_button_text_value: @code
|
37
|
+
}
|
38
|
+
) do
|
39
|
+
"Copy"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def highlighted_code
|
45
|
+
formatter = Rouge::Formatters::HTML.new
|
46
|
+
lexer = Rouge::Lexer.find(@language) || Rouge::Lexers::PlainText.new
|
47
|
+
highlighted = formatter.format(lexer.lex(@code))
|
48
|
+
|
49
|
+
tag.div(class: "overflow-x-auto rounded-lg border border-border bg-muted") do
|
50
|
+
tag.pre(class: "p-4 text-sm") do
|
51
|
+
tag.code(highlighted.html_safe, class: "language-#{@language}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docs
|
4
|
+
class ComponentApiComponent < ViewComponent::Base
|
5
|
+
def initialize
|
6
|
+
# No parameters needed for API section wrapper
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
tag.div(id: "api-reference", class: "space-y-4") do
|
11
|
+
concat(tag.h2("API Reference", class: "text-2xl font-semibold border-b border-border pb-2"))
|
12
|
+
concat(tag.div(class: "space-y-6") { content })
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docs
|
4
|
+
class ComponentExamplesComponent < ViewComponent::Base
|
5
|
+
def initialize
|
6
|
+
# No parameters needed for examples section wrapper
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
tag.div(id: "examples", class: "space-y-6") do
|
11
|
+
concat(tag.h2("Examples", class: "text-2xl font-semibold border-b border-border pb-2"))
|
12
|
+
concat(tag.div(class: "space-y-6") { content })
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docs
|
4
|
+
class ComponentHeaderComponent < ViewComponent::Base
|
5
|
+
def initialize(name:, description:)
|
6
|
+
@name = name
|
7
|
+
@description = description
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_reader :name, :description
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div id="installation" class="space-y-4">
|
2
|
+
<h2 class="text-2xl font-semibold border-b border-border pb-2">Installation</h2>
|
3
|
+
<div class="space-y-3">
|
4
|
+
<p class="text-sm text-muted-foreground">
|
5
|
+
Install the component using the M9sh CLI (with automatic dependency resolution) or by copying the files manually.
|
6
|
+
</p>
|
7
|
+
<%= render Docs::CodeBlockComponent.new(
|
8
|
+
code: "m9sh add #{component_name}",
|
9
|
+
language: "bash"
|
10
|
+
) %>
|
11
|
+
<p class="text-sm text-muted-foreground mt-4">
|
12
|
+
Or copy the component files manually from the documentation into your project's <%= render M9sh::BadgeComponent.new(variant: :secondary) do %>app/components/m9sh<% end %> directory.
|
13
|
+
</p>
|
14
|
+
</div>
|
15
|
+
</div>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docs
|
4
|
+
class ComponentPageComponent < ViewComponent::Base
|
5
|
+
renders_one :header, Docs::ComponentHeaderComponent
|
6
|
+
renders_one :installation, Docs::ComponentInstallationComponent
|
7
|
+
renders_one :usage, Docs::ComponentUsageComponent
|
8
|
+
renders_one :examples, Docs::ComponentExamplesComponent
|
9
|
+
renders_one :api, Docs::ComponentApiComponent
|
10
|
+
|
11
|
+
def initialize(title:)
|
12
|
+
@title = title
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :title
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,318 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docs
|
4
|
+
class ComponentPreviewComponent < ViewComponent::Base
|
5
|
+
attr_reader :title, :preview_content, :code, :ai_command, :mobile_optimized
|
6
|
+
|
7
|
+
def initialize(title: nil, preview_content: nil, code: nil, ai_command: nil, mobile_optimized: true)
|
8
|
+
@title = title
|
9
|
+
@preview_content = preview_content
|
10
|
+
@code = code
|
11
|
+
@ai_command = ai_command
|
12
|
+
@mobile_optimized = mobile_optimized
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
tag.div(
|
17
|
+
class: "my-6",
|
18
|
+
data: {
|
19
|
+
controller: "docs--component-preview",
|
20
|
+
docs__component_preview_default_tab_value: "preview"
|
21
|
+
}
|
22
|
+
) do
|
23
|
+
safe_join([
|
24
|
+
header_section,
|
25
|
+
preview_container
|
26
|
+
].compact)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def header_section
|
33
|
+
return unless @title
|
34
|
+
|
35
|
+
tag.div(class: "flex items-center justify-between mb-4") do
|
36
|
+
tag.h3(@title, class: "text-lg font-semibold")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def preview_container
|
41
|
+
tag.div(class: "rounded-lg border border-border bg-card overflow-hidden") do
|
42
|
+
safe_join([
|
43
|
+
tabs_header,
|
44
|
+
tabs_content
|
45
|
+
])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def tabs_header
|
50
|
+
tag.div(class: "flex items-center justify-between border-b border-border bg-muted") do
|
51
|
+
safe_join([
|
52
|
+
tabs_list,
|
53
|
+
controls_section
|
54
|
+
])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def tabs_list
|
59
|
+
tag.div(
|
60
|
+
class: "flex items-center gap-1 p-1",
|
61
|
+
data: { docs__component_preview_target: "tabList" }
|
62
|
+
) do
|
63
|
+
safe_join([
|
64
|
+
tab_trigger("Preview", "preview", true),
|
65
|
+
(@mobile_optimized ? tab_trigger("iOS", "ios") : nil),
|
66
|
+
(@mobile_optimized ? tab_trigger("Android", "android") : nil),
|
67
|
+
(@code ? tab_trigger("Code", "code") : nil),
|
68
|
+
(@ai_command ? tab_trigger("AI Commands", "ai") : nil)
|
69
|
+
].compact)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def tab_trigger(label, value, active = false)
|
74
|
+
tag.button(
|
75
|
+
label,
|
76
|
+
class: "px-3 py-1.5 text-sm font-medium rounded-md transition-colors hover:bg-accent " \
|
77
|
+
"#{active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'}",
|
78
|
+
data: {
|
79
|
+
docs__component_preview_target: "tab",
|
80
|
+
action: "click->docs--component-preview#selectTab",
|
81
|
+
tab_value: value,
|
82
|
+
state: active ? "active" : "inactive"
|
83
|
+
},
|
84
|
+
aria: { selected: active.to_s }
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def controls_section
|
89
|
+
tag.div(class: "flex items-center gap-1 p-1") do
|
90
|
+
safe_join([
|
91
|
+
copy_button,
|
92
|
+
theme_toggle_button,
|
93
|
+
fullscreen_button
|
94
|
+
])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def copy_button
|
99
|
+
return unless @code
|
100
|
+
|
101
|
+
tag.button(
|
102
|
+
class: "p-2 rounded-md hover:bg-accent transition-colors text-muted-foreground hover:text-foreground",
|
103
|
+
data: {
|
104
|
+
action: "click->docs--component-preview#copyCode",
|
105
|
+
docs__component_preview_target: "copyButton"
|
106
|
+
},
|
107
|
+
title: "Copy code"
|
108
|
+
) do
|
109
|
+
copy_icon
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def theme_toggle_button
|
114
|
+
tag.button(
|
115
|
+
class: "p-2 rounded-md hover:bg-accent transition-colors text-muted-foreground hover:text-foreground",
|
116
|
+
data: {
|
117
|
+
action: "click->docs--component-preview#toggleTheme",
|
118
|
+
docs__component_preview_target: "themeButton"
|
119
|
+
},
|
120
|
+
title: "Toggle theme"
|
121
|
+
) do
|
122
|
+
theme_icon
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def fullscreen_button
|
127
|
+
tag.button(
|
128
|
+
class: "p-2 rounded-md hover:bg-accent transition-colors text-muted-foreground hover:text-foreground",
|
129
|
+
data: {
|
130
|
+
action: "click->docs--component-preview#toggleFullscreen",
|
131
|
+
docs__component_preview_target: "fullscreenButton"
|
132
|
+
},
|
133
|
+
title: "Toggle fullscreen"
|
134
|
+
) do
|
135
|
+
fullscreen_icon
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def tabs_content
|
140
|
+
tag.div(
|
141
|
+
class: "relative",
|
142
|
+
data: { docs__component_preview_target: "previewContainer" }
|
143
|
+
) do
|
144
|
+
safe_join([
|
145
|
+
preview_panel,
|
146
|
+
(@mobile_optimized ? ios_panel : nil),
|
147
|
+
(@mobile_optimized ? android_panel : nil),
|
148
|
+
(@code ? code_panel : nil),
|
149
|
+
(@ai_command ? ai_panel : nil)
|
150
|
+
].compact)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def preview_panel
|
155
|
+
tab_panel("preview", true) do
|
156
|
+
tag.div(class: "p-8 min-h-[300px] flex items-center justify-center") do
|
157
|
+
preview_content_to_render
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def ios_panel
|
163
|
+
tab_panel("ios") do
|
164
|
+
tag.div(class: "p-8 flex items-center justify-center bg-muted") do
|
165
|
+
tag.div(class: "ios-frame relative bg-background rounded-[3rem] shadow-2xl border-[14px] border-gray-800 overflow-hidden") do
|
166
|
+
safe_join([
|
167
|
+
ios_notch,
|
168
|
+
tag.div(class: "h-[812px] w-[375px] overflow-y-auto p-4 flex items-center justify-center") do
|
169
|
+
preview_content_to_render
|
170
|
+
end
|
171
|
+
])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def android_panel
|
178
|
+
tab_panel("android") do
|
179
|
+
tag.div(class: "p-8 flex items-center justify-center bg-muted") do
|
180
|
+
tag.div(class: "android-frame relative bg-background rounded-[2rem] shadow-2xl border-[12px] border-gray-900 overflow-hidden") do
|
181
|
+
safe_join([
|
182
|
+
tag.div(class: "h-[800px] w-[360px] overflow-y-auto p-4 flex items-center justify-center") do
|
183
|
+
preview_content_to_render
|
184
|
+
end,
|
185
|
+
android_nav_bar
|
186
|
+
])
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def preview_content_to_render
|
193
|
+
# Use block content if provided, otherwise use the preview_content parameter
|
194
|
+
content.present? ? content : @preview_content
|
195
|
+
end
|
196
|
+
|
197
|
+
def code_panel
|
198
|
+
tab_panel("code") do
|
199
|
+
tag.div(class: "p-4") do
|
200
|
+
render Docs::CodeBlockComponent.new(
|
201
|
+
code: @code,
|
202
|
+
language: "erb",
|
203
|
+
show_copy: false
|
204
|
+
)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def ai_panel
|
210
|
+
tab_panel("ai") do
|
211
|
+
tag.div(class: "p-6 space-y-4") do
|
212
|
+
safe_join([
|
213
|
+
tag.p("Use this component with the following command:", class: "text-sm text-muted-foreground mb-4"),
|
214
|
+
render(Docs::CodeBlockComponent.new(
|
215
|
+
code: @ai_command,
|
216
|
+
language: "erb",
|
217
|
+
show_copy: true
|
218
|
+
))
|
219
|
+
])
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def tab_panel(value, active = false)
|
225
|
+
tag.div(
|
226
|
+
class: "#{active ? '' : 'hidden'}",
|
227
|
+
data: {
|
228
|
+
docs__component_preview_target: "panel",
|
229
|
+
panel_value: value
|
230
|
+
},
|
231
|
+
aria: { hidden: (!active).to_s }
|
232
|
+
) do
|
233
|
+
yield
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# SVG Icons
|
238
|
+
def copy_icon
|
239
|
+
tag.svg(
|
240
|
+
xmlns: "http://www.w3.org/2000/svg",
|
241
|
+
width: "16",
|
242
|
+
height: "16",
|
243
|
+
viewBox: "0 0 24 24",
|
244
|
+
fill: "none",
|
245
|
+
stroke: "currentColor",
|
246
|
+
stroke_width: "2",
|
247
|
+
stroke_linecap: "round",
|
248
|
+
stroke_linejoin: "round"
|
249
|
+
) do
|
250
|
+
safe_join([
|
251
|
+
tag.rect(x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2"),
|
252
|
+
tag.path(d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1")
|
253
|
+
])
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def theme_icon
|
258
|
+
tag.svg(
|
259
|
+
xmlns: "http://www.w3.org/2000/svg",
|
260
|
+
width: "16",
|
261
|
+
height: "16",
|
262
|
+
viewBox: "0 0 24 24",
|
263
|
+
fill: "none",
|
264
|
+
stroke: "currentColor",
|
265
|
+
stroke_width: "2",
|
266
|
+
stroke_linecap: "round",
|
267
|
+
stroke_linejoin: "round"
|
268
|
+
) do
|
269
|
+
safe_join([
|
270
|
+
tag.circle(cx: "12", cy: "12", r: "5"),
|
271
|
+
tag.line(x1: "12", y1: "1", x2: "12", y2: "3"),
|
272
|
+
tag.line(x1: "12", y1: "21", x2: "12", y2: "23"),
|
273
|
+
tag.line(x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64"),
|
274
|
+
tag.line(x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78"),
|
275
|
+
tag.line(x1: "1", y1: "12", x2: "3", y2: "12"),
|
276
|
+
tag.line(x1: "21", y1: "12", x2: "23", y2: "12"),
|
277
|
+
tag.line(x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36"),
|
278
|
+
tag.line(x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22")
|
279
|
+
])
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def fullscreen_icon
|
284
|
+
tag.svg(
|
285
|
+
xmlns: "http://www.w3.org/2000/svg",
|
286
|
+
width: "16",
|
287
|
+
height: "16",
|
288
|
+
viewBox: "0 0 24 24",
|
289
|
+
fill: "none",
|
290
|
+
stroke: "currentColor",
|
291
|
+
stroke_width: "2",
|
292
|
+
stroke_linecap: "round",
|
293
|
+
stroke_linejoin: "round"
|
294
|
+
) do
|
295
|
+
safe_join([
|
296
|
+
tag.path(d: "M8 3H5a2 2 0 0 0-2 2v3"),
|
297
|
+
tag.path(d: "M21 8V5a2 2 0 0 0-2-2h-3"),
|
298
|
+
tag.path(d: "M3 16v3a2 2 0 0 0 2 2h3"),
|
299
|
+
tag.path(d: "M16 21h3a2 2 0 0 0 2-2v-3")
|
300
|
+
])
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def ios_notch
|
305
|
+
tag.div(class: "absolute top-0 left-1/2 -translate-x-1/2 w-[200px] h-[30px] bg-gray-800 rounded-b-3xl z-10")
|
306
|
+
end
|
307
|
+
|
308
|
+
def android_nav_bar
|
309
|
+
tag.div(class: "absolute bottom-0 left-0 right-0 h-[40px] bg-gray-900 flex items-center justify-center gap-8") do
|
310
|
+
safe_join([
|
311
|
+
tag.div(class: "w-2 h-2 rounded-full bg-gray-500"),
|
312
|
+
tag.div(class: "w-2 h-2 rounded-full bg-gray-500"),
|
313
|
+
tag.div(class: "w-2 h-2 rounded-full bg-gray-500")
|
314
|
+
])
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docs
|
4
|
+
class ComponentUsageComponent < ViewComponent::Base
|
5
|
+
renders_many :code_examples
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
# No parameters needed for usage section wrapper
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
tag.div(id: "usage", class: "space-y-4") do
|
13
|
+
concat(tag.h2("Usage", class: "text-2xl font-semibold border-b border-border pb-2"))
|
14
|
+
concat(tag.div(class: "space-y-4") { content })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docs
|
4
|
+
class PropTableComponent < ViewComponent::Base
|
5
|
+
attr_reader :props
|
6
|
+
|
7
|
+
def initialize(props:)
|
8
|
+
@props = props
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
tag.div(class: "my-6 overflow-x-auto") do
|
13
|
+
tag.table(class: "w-full border-collapse") do
|
14
|
+
safe_join([
|
15
|
+
table_header,
|
16
|
+
table_body
|
17
|
+
])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def table_header
|
25
|
+
tag.thead(class: "border-b border-border") do
|
26
|
+
tag.tr do
|
27
|
+
safe_join([
|
28
|
+
tag.th("Prop", class: "px-4 py-2 text-left text-sm font-semibold"),
|
29
|
+
tag.th("Type", class: "px-4 py-2 text-left text-sm font-semibold"),
|
30
|
+
tag.th("Default", class: "px-4 py-2 text-left text-sm font-semibold"),
|
31
|
+
tag.th("Description", class: "px-4 py-2 text-left text-sm font-semibold")
|
32
|
+
])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def table_body
|
38
|
+
tag.tbody do
|
39
|
+
safe_join(
|
40
|
+
@props.map do |prop|
|
41
|
+
tag.tr(class: "border-b border-border hover:bg-muted") do
|
42
|
+
safe_join([
|
43
|
+
tag.td(class: "px-4 py-3") do
|
44
|
+
render(M9sh::BadgeComponent.new(variant: :secondary)) { prop[:name] }
|
45
|
+
end,
|
46
|
+
tag.td(class: "px-4 py-3") do
|
47
|
+
render(M9sh::BadgeComponent.new(variant: :outline)) { prop[:type] }
|
48
|
+
end,
|
49
|
+
tag.td(class: "px-4 py-3") do
|
50
|
+
if prop[:default]
|
51
|
+
render(M9sh::BadgeComponent.new(variant: :secondary)) { prop[:default] }
|
52
|
+
else
|
53
|
+
tag.span("-", class: "text-muted-foreground")
|
54
|
+
end
|
55
|
+
end,
|
56
|
+
tag.td(prop[:description], class: "px-4 py-3 text-sm text-muted-foreground")
|
57
|
+
])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|