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,51 @@
|
|
1
|
+
class BlocksController < ApplicationController
|
2
|
+
layout "backdrop"
|
3
|
+
before_action :setup_layout
|
4
|
+
|
5
|
+
BLOCKS = %w[
|
6
|
+
dashboard-01
|
7
|
+
dashboard-02
|
8
|
+
dashboard-03
|
9
|
+
settings-01
|
10
|
+
settings-02
|
11
|
+
settings-03
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
BLOCK_DESCRIPTIONS = {
|
15
|
+
'dashboard-01' => 'Comprehensive dashboard with stats cards, charts, and recent activity.',
|
16
|
+
'dashboard-02' => 'Analytics-focused dashboard with metrics, progress indicators, and activity feed.',
|
17
|
+
'dashboard-03' => 'E-commerce dashboard with orders, inventory, and customer insights.',
|
18
|
+
'settings-01' => 'Profile and account settings with form inputs, avatar upload, and preferences.',
|
19
|
+
'settings-02' => 'Appearance and display settings with theme selection and layout options.',
|
20
|
+
'settings-03' => 'Notifications and integrations with connected accounts and API access.'
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
BLOCK_CATEGORIES = {
|
24
|
+
'Dashboard' => %w[dashboard-01 dashboard-02 dashboard-03],
|
25
|
+
'Settings' => %w[settings-01 settings-02 settings-03]
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
def index
|
29
|
+
# Blocks landing page showing all available blocks
|
30
|
+
@current_page = 'index'
|
31
|
+
end
|
32
|
+
|
33
|
+
def show
|
34
|
+
@block = params[:name]
|
35
|
+
|
36
|
+
unless BLOCKS.include?(@block)
|
37
|
+
redirect_to blocks_path, alert: "Block not found"
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
render "blocks/examples/#{@block}"
|
42
|
+
rescue ActionView::MissingTemplate
|
43
|
+
redirect_to blocks_path, alert: "Block #{@block} is not yet available"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def setup_layout
|
49
|
+
@show_blocks_sidebars = true
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
class DocsController < ApplicationController
|
2
|
+
layout "backdrop"
|
3
|
+
before_action :setup_layout
|
4
|
+
|
5
|
+
GETTING_STARTED_PAGES = %w[
|
6
|
+
installation usage about
|
7
|
+
].freeze
|
8
|
+
|
9
|
+
COMPONENTS = %w[
|
10
|
+
accordion alert alert_dialog avatar badge breadcrumb button card
|
11
|
+
checkbox collapsible dialog dropdown_menu hover_card input label
|
12
|
+
main navbar navigation_menu popover progress radio_group select separator sheet sidebar
|
13
|
+
skeleton slider spinner switch table tabs textarea theme_toggle toast
|
14
|
+
toaster tooltip toggle typography
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
COMPONENT_DESCRIPTIONS = {
|
18
|
+
'accordion' => 'A vertically stacked set of interactive headings that each reveal a section of content.',
|
19
|
+
'alert' => 'Displays a callout for user attention.',
|
20
|
+
'alert_dialog' => 'A modal dialog that interrupts the user with important content and expects a response.',
|
21
|
+
'avatar' => 'An image element with a fallback for representing the user.',
|
22
|
+
'badge' => 'Displays a badge or a component that looks like a badge.',
|
23
|
+
'breadcrumb' => 'Displays the path to the current resource using a hierarchy of links.',
|
24
|
+
'button' => 'Displays a button or a component that looks like a button.',
|
25
|
+
'card' => 'Displays a card with header, content, and footer.',
|
26
|
+
'checkbox' => 'A control that allows the user to toggle between checked and not checked.',
|
27
|
+
'collapsible' => 'An interactive component which expands/collapses a panel.',
|
28
|
+
'dialog' => 'A window overlaid on either the primary window or another dialog window.',
|
29
|
+
'dropdown_menu' => 'Displays a menu to the user — such as a set of actions or functions — triggered by a button.',
|
30
|
+
'hover_card' => 'For sighted users to preview content available behind a link.',
|
31
|
+
'input' => 'Displays a form input field or a component that looks like an input field.',
|
32
|
+
'label' => 'Renders an accessible label associated with controls.',
|
33
|
+
'main' => 'A container component that creates a rounded, elevated content area with shadow effects.',
|
34
|
+
'navbar' => 'A responsive navigation header component with logo, navigation links, and action buttons.',
|
35
|
+
'navigation_menu' => 'A collection of links for navigating websites.',
|
36
|
+
'popover' => 'Displays rich content in a portal, triggered by a button.',
|
37
|
+
'progress' => 'Displays an indicator showing the completion progress of a task.',
|
38
|
+
'radio_group' => 'A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.',
|
39
|
+
'select' => 'Displays a list of options for the user to pick from—triggered by a button.',
|
40
|
+
'separator' => 'Visually or semantically separates content.',
|
41
|
+
'sheet' => 'Extends the Dialog component to display content that complements the main content of the screen.',
|
42
|
+
'sidebar' => 'A composable, themeable sidebar component with collapsible navigation and responsive behavior.',
|
43
|
+
'skeleton' => 'Use to show a placeholder while content is loading.',
|
44
|
+
'slider' => 'An input where the user selects a value from within a given range.',
|
45
|
+
'spinner' => 'An indicator that can be used to show a loading state.',
|
46
|
+
'switch' => 'A control that allows the user to toggle between checked and not checked.',
|
47
|
+
'table' => 'A responsive table component.',
|
48
|
+
'tabs' => 'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
|
49
|
+
'textarea' => 'Displays a form textarea or a component that looks like a textarea.',
|
50
|
+
'theme_toggle' => 'A toggle button to switch between light and dark themes.',
|
51
|
+
'toast' => 'A succinct message that is displayed temporarily.',
|
52
|
+
'toaster' => 'The provider component for the toast notification system.',
|
53
|
+
'tooltip' => 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
|
54
|
+
'toggle' => 'A two-state button that can be either on or off.',
|
55
|
+
'typography' => 'Styles for headings, paragraphs, lists, and other text elements.'
|
56
|
+
}.freeze
|
57
|
+
|
58
|
+
def index
|
59
|
+
# Introduction page
|
60
|
+
@current_page = 'introduction'
|
61
|
+
end
|
62
|
+
|
63
|
+
def installation
|
64
|
+
@current_page = 'installation'
|
65
|
+
end
|
66
|
+
|
67
|
+
def usage
|
68
|
+
@current_page = 'usage'
|
69
|
+
|
70
|
+
@short_syntax_code = %q{<%# Button %>
|
71
|
+
<%= ui_button(variant: :outline, class: "w-full") do %>
|
72
|
+
Click me
|
73
|
+
<% end %>
|
74
|
+
|
75
|
+
<%# Badge %>
|
76
|
+
<%= ui_badge(variant: :secondary) do %>
|
77
|
+
New
|
78
|
+
<% end %>
|
79
|
+
|
80
|
+
<%# Input %>
|
81
|
+
<%= ui_input(type: "email", placeholder: "Enter your email") %>
|
82
|
+
|
83
|
+
<%# Card with slots %>
|
84
|
+
<%= ui_card(class: "w-full") do |card| %>
|
85
|
+
<% card.with_header do %>
|
86
|
+
<h3 class="font-semibold">Card Title</h3>
|
87
|
+
<% end %>
|
88
|
+
<% card.with_body do %>
|
89
|
+
Card content goes here.
|
90
|
+
<% end %>
|
91
|
+
<% end %>}
|
92
|
+
|
93
|
+
@backwards_syntax_code = %q{<%# These are aliases to the ui_* methods %>
|
94
|
+
<%= render_button(variant: :outline) do %>
|
95
|
+
Click me
|
96
|
+
<% end %>
|
97
|
+
|
98
|
+
<%= render_badge(variant: :secondary) do %>
|
99
|
+
New
|
100
|
+
<% end %>
|
101
|
+
|
102
|
+
<%= render_input(type: "email", placeholder: "Enter your email") %>}
|
103
|
+
|
104
|
+
@original_syntax_code = %q{<%# Full ViewComponent syntax %>
|
105
|
+
<%= render M9sh::ButtonComponent.new(variant: :outline, class: "w-full") do %>
|
106
|
+
Click me
|
107
|
+
<% end %>
|
108
|
+
|
109
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
110
|
+
New
|
111
|
+
<% end %>
|
112
|
+
|
113
|
+
<%= render M9sh::InputComponent.new(type: "email", placeholder: "Enter your email") %>}
|
114
|
+
|
115
|
+
@comparison_backwards_code = %q{<%= render_button(variant: :outline, class: "w-full") do %>
|
116
|
+
Submit
|
117
|
+
<% end %>}
|
118
|
+
|
119
|
+
@comparison_original_code = %q{<%= render M9sh::ButtonComponent.new(variant: :outline, class: "w-full") do %>
|
120
|
+
Submit
|
121
|
+
<% end %>}
|
122
|
+
|
123
|
+
@inline_short_example = %q{<%= ui_button(variant: :outline, class: "w-full") { "Submit" } %>}
|
124
|
+
@inline_block_example = %q{<%= ui_button(variant: :outline) { "Click me" } %>}
|
125
|
+
@custom_class_example = %q{<%= ui_button(variant: :outline, class: "w-full mt-4") { "Full Width" } %>}
|
126
|
+
end
|
127
|
+
|
128
|
+
def about
|
129
|
+
@current_page = 'about'
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_accordion
|
133
|
+
# Test page for accordion
|
134
|
+
end
|
135
|
+
|
136
|
+
def debug_accordion
|
137
|
+
# Debug page for accordion
|
138
|
+
end
|
139
|
+
|
140
|
+
def simple_test
|
141
|
+
# Simple test page
|
142
|
+
end
|
143
|
+
|
144
|
+
def component
|
145
|
+
@component = params[:name]
|
146
|
+
|
147
|
+
unless COMPONENTS.include?(@component)
|
148
|
+
redirect_to docs_path, alert: "Component not found"
|
149
|
+
return
|
150
|
+
end
|
151
|
+
|
152
|
+
render "docs/components/#{@component}"
|
153
|
+
rescue ActionView::MissingTemplate
|
154
|
+
redirect_to docs_path, alert: "Documentation for #{@component} is not yet available"
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def setup_layout
|
160
|
+
@show_docs_sidebars = true
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class ShowcaseController < ApplicationController
|
2
|
+
def index
|
3
|
+
# Main showcase page with all components
|
4
|
+
end
|
5
|
+
|
6
|
+
def v2
|
7
|
+
# New showcase v2 - uses application layout with m9sh components
|
8
|
+
@components = DocsController::COMPONENTS
|
9
|
+
@descriptions = DocsController::COMPONENT_DESCRIPTIONS
|
10
|
+
end
|
11
|
+
|
12
|
+
def components
|
13
|
+
# Components gallery page
|
14
|
+
@components = DocsController::COMPONENTS
|
15
|
+
@descriptions = DocsController::COMPONENT_DESCRIPTIONS
|
16
|
+
render layout: false
|
17
|
+
end
|
18
|
+
|
19
|
+
def buttons
|
20
|
+
# Button component examples
|
21
|
+
end
|
22
|
+
|
23
|
+
def cards
|
24
|
+
# Card component examples
|
25
|
+
end
|
26
|
+
|
27
|
+
def forms
|
28
|
+
# Form component examples
|
29
|
+
end
|
30
|
+
|
31
|
+
def accordion
|
32
|
+
# Accordion component examples
|
33
|
+
end
|
34
|
+
|
35
|
+
def dialog
|
36
|
+
# Dialog component examples
|
37
|
+
end
|
38
|
+
|
39
|
+
def tabs
|
40
|
+
# Tabs component examples
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlocksHelper
|
4
|
+
# Extract code from the current view file between markers
|
5
|
+
def extract_block_code(start_marker, end_marker)
|
6
|
+
view_file = caller_locations.find { |loc| loc.path.include?('app/views/blocks/examples') }&.path
|
7
|
+
return "" unless view_file && File.exist?(view_file)
|
8
|
+
|
9
|
+
content = File.read(view_file)
|
10
|
+
start_index = content.index(start_marker)
|
11
|
+
end_index = content.index(end_marker, start_index) if start_index
|
12
|
+
|
13
|
+
return "" unless start_index && end_index
|
14
|
+
|
15
|
+
# Extract the code between markers and clean it up
|
16
|
+
code = content[start_index + start_marker.length...end_index]
|
17
|
+
|
18
|
+
# Remove leading whitespace while preserving relative indentation
|
19
|
+
lines = code.lines
|
20
|
+
return "" if lines.empty?
|
21
|
+
|
22
|
+
# Find minimum indentation (excluding empty lines)
|
23
|
+
min_indent = lines.reject { |line| line.strip.empty? }
|
24
|
+
.map { |line| line[/^\s*/].length }
|
25
|
+
.min || 0
|
26
|
+
|
27
|
+
# Remove the minimum indentation from all lines
|
28
|
+
lines.map { |line| line.strip.empty? ? line : line[min_indent..] || line }.join.strip
|
29
|
+
end
|
30
|
+
def dashboard_01_code
|
31
|
+
<<~'CODE'
|
32
|
+
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
33
|
+
<!-- Stats Cards -->
|
34
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
35
|
+
<% card.with_header(class: "flex flex-row items-center justify-between pb-2") do %>
|
36
|
+
<h3 class="text-sm font-medium text-muted-foreground">Total Revenue</h3>
|
37
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
|
38
|
+
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
39
|
+
</svg>
|
40
|
+
<% end %>
|
41
|
+
<% card.with_body do %>
|
42
|
+
<div class="text-2xl font-bold">$45,231.89</div>
|
43
|
+
<p class="text-xs text-muted-foreground">+20.1% from last month</p>
|
44
|
+
<% end %>
|
45
|
+
<% end %>
|
46
|
+
|
47
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
48
|
+
<% card.with_header(class: "flex flex-row items-center justify-between pb-2") do %>
|
49
|
+
<h3 class="text-sm font-medium text-muted-foreground">Subscriptions</h3>
|
50
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
|
51
|
+
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
52
|
+
<circle cx="9" cy="7" r="4" />
|
53
|
+
<path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
|
54
|
+
</svg>
|
55
|
+
<% end %>
|
56
|
+
<% card.with_body do %>
|
57
|
+
<div class="text-2xl font-bold">+2350</div>
|
58
|
+
<p class="text-xs text-muted-foreground">+180.1% from last month</p>
|
59
|
+
<% end %>
|
60
|
+
<% end %>
|
61
|
+
|
62
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
63
|
+
<% card.with_header(class: "flex flex-row items-center justify-between pb-2") do %>
|
64
|
+
<h3 class="text-sm font-medium text-muted-foreground">Sales</h3>
|
65
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
|
66
|
+
<rect width="20" height="14" x="2" y="5" rx="2" />
|
67
|
+
<path d="M2 10h20" />
|
68
|
+
</svg>
|
69
|
+
<% end %>
|
70
|
+
<% card.with_body do %>
|
71
|
+
<div class="text-2xl font-bold">+12,234</div>
|
72
|
+
<p class="text-xs text-muted-foreground">+19% from last month</p>
|
73
|
+
<% end %>
|
74
|
+
<% end %>
|
75
|
+
|
76
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
77
|
+
<% card.with_header(class: "flex flex-row items-center justify-between pb-2") do %>
|
78
|
+
<h3 class="text-sm font-medium text-muted-foreground">Active Now</h3>
|
79
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="h-4 w-4 text-muted-foreground">
|
80
|
+
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
81
|
+
</svg>
|
82
|
+
<% end %>
|
83
|
+
<% card.with_body do %>
|
84
|
+
<div class="text-2xl font-bold">+573</div>
|
85
|
+
<p class="text-xs text-muted-foreground">+201 since last hour</p>
|
86
|
+
<% end %>
|
87
|
+
<% end %>
|
88
|
+
</div>
|
89
|
+
CODE
|
90
|
+
end
|
91
|
+
|
92
|
+
def dashboard_02_code
|
93
|
+
<<~'CODE'
|
94
|
+
<!-- Top Metrics Row -->
|
95
|
+
<div class="grid gap-4 md:grid-cols-3">
|
96
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
97
|
+
<% card.with_header do |header| %>
|
98
|
+
<% header.with_title { "Total Users" } %>
|
99
|
+
<% header.with_description { "Active users this month" } %>
|
100
|
+
<% end %>
|
101
|
+
<% card.with_body do %>
|
102
|
+
<div class="text-3xl font-bold">12,543</div>
|
103
|
+
<p class="text-xs text-muted-foreground mt-2">
|
104
|
+
<%= render M9sh::BadgeComponent.new(variant: :default) { "+12.5%" } %>
|
105
|
+
from last month
|
106
|
+
</p>
|
107
|
+
<% end %>
|
108
|
+
<% end %>
|
109
|
+
<!-- Add more metric cards... -->
|
110
|
+
</div>
|
111
|
+
|
112
|
+
<!-- Activity Feed and Goals -->
|
113
|
+
<div class="grid gap-4 md:grid-cols-2">
|
114
|
+
<!-- Activity Feed -->
|
115
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
116
|
+
<% card.with_header do |header| %>
|
117
|
+
<% header.with_title { "Recent Activity" } %>
|
118
|
+
<% end %>
|
119
|
+
<% card.with_body do %>
|
120
|
+
<!-- Activity items with avatars and badges -->
|
121
|
+
<% end %>
|
122
|
+
<% end %>
|
123
|
+
|
124
|
+
<!-- Goals Progress -->
|
125
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
126
|
+
<% card.with_header do |header| %>
|
127
|
+
<% header.with_title { "Monthly Goals" } %>
|
128
|
+
<% end %>
|
129
|
+
<% card.with_body do %>
|
130
|
+
<%= render M9sh::ProgressComponent.new(value: 84.2) %>
|
131
|
+
<!-- More progress indicators -->
|
132
|
+
<% end %>
|
133
|
+
<% end %>
|
134
|
+
</div>
|
135
|
+
CODE
|
136
|
+
end
|
137
|
+
|
138
|
+
def dashboard_03_code
|
139
|
+
<<~'CODE'
|
140
|
+
<!-- Quick Stats -->
|
141
|
+
<div class="grid gap-4 md:grid-cols-4">
|
142
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
143
|
+
<% card.with_body do %>
|
144
|
+
<div class="flex items-center justify-between">
|
145
|
+
<div>
|
146
|
+
<p class="text-sm font-medium text-muted-foreground">Total Sales</p>
|
147
|
+
<p class="text-2xl font-bold mt-1">$54,239</p>
|
148
|
+
</div>
|
149
|
+
<!-- Icon SVG -->
|
150
|
+
</div>
|
151
|
+
<% end %>
|
152
|
+
<% end %>
|
153
|
+
<!-- Add more stat cards... -->
|
154
|
+
</div>
|
155
|
+
|
156
|
+
<!-- Recent Orders and Top Products -->
|
157
|
+
<div class="grid gap-4 md:grid-cols-2">
|
158
|
+
<!-- Orders Table -->
|
159
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
160
|
+
<% card.with_header do |header| %>
|
161
|
+
<% header.with_title { "Recent Orders" } %>
|
162
|
+
<% end %>
|
163
|
+
<% card.with_body do %>
|
164
|
+
<%= render M9sh::TableComponent.new do |table| %>
|
165
|
+
<!-- Table content -->
|
166
|
+
<% end %>
|
167
|
+
<% end %>
|
168
|
+
<% end %>
|
169
|
+
|
170
|
+
<!-- Top Products -->
|
171
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
172
|
+
<% card.with_header do |header| %>
|
173
|
+
<% header.with_title { "Top Products" } %>
|
174
|
+
<% end %>
|
175
|
+
<% card.with_body do %>
|
176
|
+
<!-- Product list with badges -->
|
177
|
+
<% end %>
|
178
|
+
<% end %>
|
179
|
+
</div>
|
180
|
+
CODE
|
181
|
+
end
|
182
|
+
|
183
|
+
def settings_01_code
|
184
|
+
<<~'CODE'
|
185
|
+
<!-- Profile Section -->
|
186
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
187
|
+
<% card.with_header do |header| %>
|
188
|
+
<% header.with_title { "Profile" } %>
|
189
|
+
<% header.with_description { "Update your personal information and profile picture." } %>
|
190
|
+
<% end %>
|
191
|
+
<% card.with_body do %>
|
192
|
+
<div class="space-y-6">
|
193
|
+
<!-- Avatar Upload -->
|
194
|
+
<div class="flex items-center gap-4">
|
195
|
+
<%= render M9sh::AvatarComponent.new(fallback: "JD", class: "h-20 w-20") %>
|
196
|
+
<%= render M9sh::ButtonComponent.new(variant: :outline, size: :sm) { "Change Avatar" } %>
|
197
|
+
</div>
|
198
|
+
|
199
|
+
<!-- Form Fields -->
|
200
|
+
<div class="grid gap-4 md:grid-cols-2">
|
201
|
+
<div class="space-y-2">
|
202
|
+
<%= render M9sh::LabelComponent.new(for: "first-name") { "First Name" } %>
|
203
|
+
<%= render M9sh::InputComponent.new(id: "first-name", placeholder: "John") %>
|
204
|
+
</div>
|
205
|
+
<div class="space-y-2">
|
206
|
+
<%= render M9sh::LabelComponent.new(for: "last-name") { "Last Name" } %>
|
207
|
+
<%= render M9sh::InputComponent.new(id: "last-name", placeholder: "Doe") %>
|
208
|
+
</div>
|
209
|
+
</div>
|
210
|
+
|
211
|
+
<div class="space-y-2">
|
212
|
+
<%= render M9sh::LabelComponent.new(for: "email") { "Email" } %>
|
213
|
+
<%= render M9sh::InputComponent.new(id: "email", type: :email) %>
|
214
|
+
</div>
|
215
|
+
</div>
|
216
|
+
<% end %>
|
217
|
+
<% card.with_footer do %>
|
218
|
+
<%= render M9sh::ButtonComponent.new { "Save Changes" } %>
|
219
|
+
<% end %>
|
220
|
+
<% end %>
|
221
|
+
|
222
|
+
<!-- Preferences with Switches -->
|
223
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
224
|
+
<% card.with_header do |header| %>
|
225
|
+
<% header.with_title { "Preferences" } %>
|
226
|
+
<% end %>
|
227
|
+
<% card.with_body do %>
|
228
|
+
<div class="flex items-center justify-between">
|
229
|
+
<div class="space-y-0.5">
|
230
|
+
<%= render M9sh::LabelComponent.new(for: "marketing") { "Marketing Emails" } %>
|
231
|
+
<p class="text-sm text-muted-foreground">Receive emails about new products.</p>
|
232
|
+
</div>
|
233
|
+
<%= render M9sh::SwitchComponent.new(id: "marketing") %>
|
234
|
+
</div>
|
235
|
+
<% end %>
|
236
|
+
<% end %>
|
237
|
+
CODE
|
238
|
+
end
|
239
|
+
|
240
|
+
def settings_02_code
|
241
|
+
<<~'CODE'
|
242
|
+
<!-- Theme Selection -->
|
243
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
244
|
+
<% card.with_header do |header| %>
|
245
|
+
<% header.with_title { "Theme" } %>
|
246
|
+
<% header.with_description { "Select your preferred color theme." } %>
|
247
|
+
<% end %>
|
248
|
+
<% card.with_body do %>
|
249
|
+
<%= render M9sh::RadioGroupComponent.new(name: "theme", default_value: "light") do |group| %>
|
250
|
+
<div class="grid gap-4 md:grid-cols-3">
|
251
|
+
<label class="border rounded-lg p-4 cursor-pointer hover:bg-accent">
|
252
|
+
<%= group.radio_item(value: "light", id: "theme-light") %>
|
253
|
+
<p class="font-medium">Light</p>
|
254
|
+
<p class="text-sm text-muted-foreground">Clean and bright</p>
|
255
|
+
</label>
|
256
|
+
<label class="border rounded-lg p-4 cursor-pointer hover:bg-accent">
|
257
|
+
<%= group.radio_item(value: "dark", id: "theme-dark") %>
|
258
|
+
<p class="font-medium">Dark</p>
|
259
|
+
<p class="text-sm text-muted-foreground">Easy on the eyes</p>
|
260
|
+
</label>
|
261
|
+
<label class="border rounded-lg p-4 cursor-pointer hover:bg-accent">
|
262
|
+
<%= group.radio_item(value: "system", id: "theme-system") %>
|
263
|
+
<p class="font-medium">System</p>
|
264
|
+
<p class="text-sm text-muted-foreground">Use system preference</p>
|
265
|
+
</label>
|
266
|
+
</div>
|
267
|
+
<% end %>
|
268
|
+
<% end %>
|
269
|
+
<% end %>
|
270
|
+
|
271
|
+
<!-- Layout Options -->
|
272
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
273
|
+
<% card.with_header do |header| %>
|
274
|
+
<% header.with_title { "Layout" } %>
|
275
|
+
<% end %>
|
276
|
+
<% card.with_body do %>
|
277
|
+
<div class="space-y-4">
|
278
|
+
<div class="flex items-start space-x-3">
|
279
|
+
<%= render M9sh::CheckboxComponent.new(id: "compact") %>
|
280
|
+
<div class="space-y-1">
|
281
|
+
<%= render M9sh::LabelComponent.new(for: "compact") { "Compact Mode" } %>
|
282
|
+
<p class="text-sm text-muted-foreground">Reduce spacing between elements.</p>
|
283
|
+
</div>
|
284
|
+
</div>
|
285
|
+
</div>
|
286
|
+
<% end %>
|
287
|
+
<% card.with_footer do %>
|
288
|
+
<%= render M9sh::ButtonComponent.new { "Save Preferences" } %>
|
289
|
+
<% end %>
|
290
|
+
<% end %>
|
291
|
+
CODE
|
292
|
+
end
|
293
|
+
|
294
|
+
def settings_03_code
|
295
|
+
<<~'CODE'
|
296
|
+
<%= render M9sh::TabsComponent.new(default_value: "notifications") do |tabs| %>
|
297
|
+
<% tabs.with_tabs_list do %>
|
298
|
+
<%= tabs.tabs_trigger(value: "notifications") { "Notifications" } %>
|
299
|
+
<%= tabs.tabs_trigger(value: "integrations") { "Integrations" } %>
|
300
|
+
<% end %>
|
301
|
+
|
302
|
+
<!-- Notifications Tab -->
|
303
|
+
<% tabs.with_tabs_content(value: "notifications") do %>
|
304
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
305
|
+
<% card.with_header do |header| %>
|
306
|
+
<% header.with_title { "Email Notifications" } %>
|
307
|
+
<% end %>
|
308
|
+
<% card.with_body do %>
|
309
|
+
<div class="flex items-center justify-between">
|
310
|
+
<div class="space-y-0.5">
|
311
|
+
<%= render M9sh::LabelComponent.new(for: "comments") { "Comments" } %>
|
312
|
+
<p class="text-sm text-muted-foreground">Notifications about comments.</p>
|
313
|
+
</div>
|
314
|
+
<%= render M9sh::SwitchComponent.new(id: "comments", checked: true) %>
|
315
|
+
</div>
|
316
|
+
<% end %>
|
317
|
+
<% end %>
|
318
|
+
<% end %>
|
319
|
+
|
320
|
+
<!-- Integrations Tab -->
|
321
|
+
<% tabs.with_tabs_content(value: "integrations") do %>
|
322
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
323
|
+
<% card.with_header do |header| %>
|
324
|
+
<% header.with_title { "Connected Accounts" } %>
|
325
|
+
<% end %>
|
326
|
+
<% card.with_body do %>
|
327
|
+
<div class="flex items-center justify-between">
|
328
|
+
<div class="flex items-center gap-3">
|
329
|
+
<div class="h-10 w-10 rounded-full bg-muted"></div>
|
330
|
+
<div>
|
331
|
+
<p class="font-medium">GitHub</p>
|
332
|
+
<%= render M9sh::BadgeComponent.new { "Connected" } %>
|
333
|
+
</div>
|
334
|
+
</div>
|
335
|
+
<%= render M9sh::ButtonComponent.new(variant: :outline, size: :sm) { "Disconnect" } %>
|
336
|
+
</div>
|
337
|
+
<% end %>
|
338
|
+
<% end %>
|
339
|
+
<% end %>
|
340
|
+
<% end %>
|
341
|
+
CODE
|
342
|
+
end
|
343
|
+
end
|