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,149 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["tab", "panel", "tabList", "previewContainer", "copyButton", "themeButton", "fullscreenButton"]
|
5
|
+
static values = {
|
6
|
+
defaultTab: String,
|
7
|
+
code: String
|
8
|
+
}
|
9
|
+
|
10
|
+
connect() {
|
11
|
+
// Initialize with default tab
|
12
|
+
if (this.hasDefaultTabValue) {
|
13
|
+
this.selectTabByValue(this.defaultTabValue)
|
14
|
+
} else if (this.tabTargets.length > 0) {
|
15
|
+
// Select first tab if no default
|
16
|
+
const firstValue = this.tabTargets[0].dataset.tabValue
|
17
|
+
this.selectTabByValue(firstValue)
|
18
|
+
}
|
19
|
+
|
20
|
+
// Store original code for copying
|
21
|
+
this.setupCodeForCopying()
|
22
|
+
}
|
23
|
+
|
24
|
+
setupCodeForCopying() {
|
25
|
+
// Find code in the code panel
|
26
|
+
const codePanel = this.panelTargets.find(panel => panel.dataset.panelValue === "code")
|
27
|
+
if (codePanel) {
|
28
|
+
const codeElement = codePanel.querySelector("pre code")
|
29
|
+
if (codeElement) {
|
30
|
+
// Store the text content for copying (strip HTML from Rouge highlighting)
|
31
|
+
this.codeText = codeElement.textContent
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
selectTab(event) {
|
37
|
+
event.preventDefault()
|
38
|
+
const tab = event.currentTarget
|
39
|
+
const value = tab.dataset.tabValue
|
40
|
+
|
41
|
+
this.selectTabByValue(value)
|
42
|
+
}
|
43
|
+
|
44
|
+
selectTabByValue(value) {
|
45
|
+
// Update tabs
|
46
|
+
this.tabTargets.forEach(tab => {
|
47
|
+
const isActive = tab.dataset.tabValue === value
|
48
|
+
tab.dataset.state = isActive ? "active" : "inactive"
|
49
|
+
tab.setAttribute("aria-selected", isActive.toString())
|
50
|
+
|
51
|
+
if (isActive) {
|
52
|
+
tab.classList.remove("text-muted-foreground")
|
53
|
+
tab.classList.add("bg-background", "text-foreground", "shadow-sm")
|
54
|
+
} else {
|
55
|
+
tab.classList.remove("bg-background", "text-foreground", "shadow-sm")
|
56
|
+
tab.classList.add("text-muted-foreground")
|
57
|
+
}
|
58
|
+
})
|
59
|
+
|
60
|
+
// Update panels
|
61
|
+
this.panelTargets.forEach(panel => {
|
62
|
+
const isVisible = panel.dataset.panelValue === value
|
63
|
+
|
64
|
+
if (isVisible) {
|
65
|
+
panel.classList.remove("hidden")
|
66
|
+
panel.setAttribute("aria-hidden", "false")
|
67
|
+
} else {
|
68
|
+
panel.classList.add("hidden")
|
69
|
+
panel.setAttribute("aria-hidden", "true")
|
70
|
+
}
|
71
|
+
})
|
72
|
+
}
|
73
|
+
|
74
|
+
copyCode() {
|
75
|
+
if (!this.codeText) {
|
76
|
+
console.warn("No code to copy")
|
77
|
+
return
|
78
|
+
}
|
79
|
+
|
80
|
+
navigator.clipboard.writeText(this.codeText).then(() => {
|
81
|
+
// Update button with feedback
|
82
|
+
if (this.hasCopyButtonTarget) {
|
83
|
+
const button = this.copyButtonTarget
|
84
|
+
const originalHTML = button.innerHTML
|
85
|
+
|
86
|
+
button.innerHTML = this.getCheckIcon()
|
87
|
+
button.classList.add("text-green-600")
|
88
|
+
|
89
|
+
setTimeout(() => {
|
90
|
+
button.innerHTML = originalHTML
|
91
|
+
button.classList.remove("text-green-600")
|
92
|
+
}, 2000)
|
93
|
+
}
|
94
|
+
}).catch(err => {
|
95
|
+
console.error("Failed to copy code:", err)
|
96
|
+
})
|
97
|
+
}
|
98
|
+
|
99
|
+
toggleTheme() {
|
100
|
+
if (!this.hasPreviewContainerTarget) return
|
101
|
+
|
102
|
+
const container = this.previewContainerTarget
|
103
|
+
container.classList.toggle("dark")
|
104
|
+
|
105
|
+
// Update button icon or state if needed
|
106
|
+
if (this.hasThemeButtonTarget) {
|
107
|
+
const isDark = container.classList.contains("dark")
|
108
|
+
const button = this.themeButtonTarget
|
109
|
+
|
110
|
+
if (isDark) {
|
111
|
+
button.classList.add("text-primary")
|
112
|
+
} else {
|
113
|
+
button.classList.remove("text-primary")
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
toggleFullscreen() {
|
119
|
+
if (!this.element) return
|
120
|
+
|
121
|
+
if (!document.fullscreenElement) {
|
122
|
+
this.element.requestFullscreen().catch(err => {
|
123
|
+
console.error("Error attempting to enable fullscreen:", err)
|
124
|
+
})
|
125
|
+
} else {
|
126
|
+
document.exitFullscreen()
|
127
|
+
}
|
128
|
+
|
129
|
+
// Listen for fullscreen change to update button
|
130
|
+
document.addEventListener("fullscreenchange", () => {
|
131
|
+
if (this.hasFullscreenButtonTarget) {
|
132
|
+
const button = this.fullscreenButtonTarget
|
133
|
+
if (document.fullscreenElement) {
|
134
|
+
button.classList.add("text-primary")
|
135
|
+
} else {
|
136
|
+
button.classList.remove("text-primary")
|
137
|
+
}
|
138
|
+
}
|
139
|
+
})
|
140
|
+
}
|
141
|
+
|
142
|
+
getCheckIcon() {
|
143
|
+
return `
|
144
|
+
<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">
|
145
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
146
|
+
</svg>
|
147
|
+
`
|
148
|
+
}
|
149
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static values = {
|
5
|
+
text: String
|
6
|
+
}
|
7
|
+
|
8
|
+
copy() {
|
9
|
+
navigator.clipboard.writeText(this.textValue).then(() => {
|
10
|
+
const originalText = this.element.textContent
|
11
|
+
this.element.textContent = "Copied!"
|
12
|
+
|
13
|
+
setTimeout(() => {
|
14
|
+
this.element.textContent = originalText
|
15
|
+
}, 2000)
|
16
|
+
}).catch(err => {
|
17
|
+
console.error("Failed to copy:", err)
|
18
|
+
})
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
// Import and register all your controllers from the importmap via controllers/**/*_controller
|
2
|
+
import { application } from "./application"
|
3
|
+
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
|
4
|
+
|
5
|
+
// Eager load all controllers defined in the import map under controllers/**/*_controller
|
6
|
+
eagerLoadControllersFrom("controllers", application)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
connect() {
|
5
|
+
// Initialize theme on connect
|
6
|
+
this.updateTheme()
|
7
|
+
}
|
8
|
+
|
9
|
+
toggle() {
|
10
|
+
const html = document.documentElement
|
11
|
+
const isDark = html.classList.toggle('dark')
|
12
|
+
localStorage.setItem('theme', isDark ? 'dark' : 'light')
|
13
|
+
}
|
14
|
+
|
15
|
+
updateTheme() {
|
16
|
+
const theme = localStorage.getItem('theme') || 'light'
|
17
|
+
if (theme === 'dark') {
|
18
|
+
document.documentElement.classList.add('dark')
|
19
|
+
} else {
|
20
|
+
document.documentElement.classList.remove('dark')
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<nav class="px-12 py-8 space-y-8">
|
2
|
+
<!-- Overview Section -->
|
3
|
+
<div>
|
4
|
+
<h4 class="mb-2 text-sm font-semibold">Overview</h4>
|
5
|
+
<ul class="space-y-0.5">
|
6
|
+
<li>
|
7
|
+
<a href="<%= blocks_path %>"
|
8
|
+
class="block px-3 py-1.5 text-sm rounded-lg transition-colors <%= @current_page == 'index' ? 'bg-sidebar-accent font-medium' : 'hover:bg-sidebar-accent' %>">
|
9
|
+
All Blocks
|
10
|
+
</a>
|
11
|
+
</li>
|
12
|
+
</ul>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<!-- Blocks by Category -->
|
16
|
+
<% BlocksController::BLOCK_CATEGORIES.each do |category, blocks| %>
|
17
|
+
<div>
|
18
|
+
<h4 class="mb-2 text-sm font-semibold"><%= category %></h4>
|
19
|
+
<ul class="space-y-0.5">
|
20
|
+
<% blocks.each do |block| %>
|
21
|
+
<li>
|
22
|
+
<a href="<%= block_path(block) %>"
|
23
|
+
class="block px-3 py-1.5 text-sm rounded-lg transition-colors <%= @block == block ? 'bg-sidebar-accent font-medium' : 'hover:bg-sidebar-accent' %>">
|
24
|
+
<%= block.titleize %>
|
25
|
+
</a>
|
26
|
+
</li>
|
27
|
+
<% end %>
|
28
|
+
</ul>
|
29
|
+
</div>
|
30
|
+
<% end %>
|
31
|
+
</nav>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<div class="px-12 py-8">
|
2
|
+
<p class="font-semibold text-sm mb-2 px-3">On This Page</p>
|
3
|
+
<nav class="space-y-0.5">
|
4
|
+
<% if @block.present? %>
|
5
|
+
<a href="#overview" class="block text-sm px-3 py-1.5 rounded-lg hover:bg-sidebar-accent transition-colors">
|
6
|
+
Overview
|
7
|
+
</a>
|
8
|
+
<a href="#preview" class="block text-sm px-3 py-1.5 rounded-lg hover:bg-sidebar-accent transition-colors">
|
9
|
+
Preview
|
10
|
+
</a>
|
11
|
+
<a href="#code" class="block text-sm px-3 py-1.5 rounded-lg hover:bg-sidebar-accent transition-colors">
|
12
|
+
Code
|
13
|
+
</a>
|
14
|
+
<a href="#usage" class="block text-sm px-3 py-1.5 rounded-lg hover:bg-sidebar-accent transition-colors">
|
15
|
+
Usage
|
16
|
+
</a>
|
17
|
+
<% elsif @current_page == 'index' %>
|
18
|
+
<a href="#hero" class="block text-sm px-3 py-1.5 rounded-lg hover:bg-sidebar-accent transition-colors">
|
19
|
+
Overview
|
20
|
+
</a>
|
21
|
+
<a href="#dashboard" class="block text-sm px-3 py-1.5 rounded-lg hover:bg-sidebar-accent transition-colors">
|
22
|
+
Dashboard
|
23
|
+
</a>
|
24
|
+
<a href="#about" class="block text-sm px-3 py-1.5 rounded-lg hover:bg-sidebar-accent transition-colors">
|
25
|
+
About
|
26
|
+
</a>
|
27
|
+
<% end %>
|
28
|
+
</nav>
|
29
|
+
</div>
|
@@ -0,0 +1,180 @@
|
|
1
|
+
<% content_for :title, "Dashboard 01 - Blocks" %>
|
2
|
+
|
3
|
+
<!-- Page Header -->
|
4
|
+
<div class="space-y-2 mb-8">
|
5
|
+
<h1 class="text-3xl font-bold tracking-tight">Dashboard 01</h1>
|
6
|
+
<p class="text-muted-foreground">
|
7
|
+
A comprehensive dashboard with stats cards, charts, and recent activity. Perfect for admin panels and analytics interfaces.
|
8
|
+
</p>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<!-- Dashboard Preview -->
|
12
|
+
<% dashboard_html = capture do %>
|
13
|
+
<div class="w-full space-y-6">
|
14
|
+
<!-- Stats Cards Grid -->
|
15
|
+
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
16
|
+
<!-- Total Revenue Card -->
|
17
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
18
|
+
<% card.with_header(class: "!pb-2") do |header| %>
|
19
|
+
<% header.with_title do %>
|
20
|
+
<h3 class="text-sm font-medium text-muted-foreground">Total Revenue</h3>
|
21
|
+
<% end %>
|
22
|
+
<% header.with_action do %>
|
23
|
+
<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">
|
24
|
+
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
25
|
+
</svg>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
28
|
+
<% card.with_body do %>
|
29
|
+
<div class="text-2xl font-bold">$45,231.89</div>
|
30
|
+
<p class="text-xs text-muted-foreground">+20.1% from last month</p>
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
|
34
|
+
<!-- Subscriptions Card -->
|
35
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
36
|
+
<% card.with_header(class: "!pb-2") do |header| %>
|
37
|
+
<% header.with_title do %>
|
38
|
+
<h3 class="text-sm font-medium text-muted-foreground">Subscriptions</h3>
|
39
|
+
<% end %>
|
40
|
+
<% header.with_action do %>
|
41
|
+
<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">
|
42
|
+
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
43
|
+
<circle cx="9" cy="7" r="4" />
|
44
|
+
<path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
|
45
|
+
</svg>
|
46
|
+
<% end %>
|
47
|
+
<% end %>
|
48
|
+
<% card.with_body do %>
|
49
|
+
<div class="text-2xl font-bold">+2,350</div>
|
50
|
+
<p class="text-xs text-muted-foreground">+180.1% from last month</p>
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
|
54
|
+
<!-- Sales Card -->
|
55
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
56
|
+
<% card.with_header(class: "!pb-2") do |header| %>
|
57
|
+
<% header.with_title do %>
|
58
|
+
<h3 class="text-sm font-medium text-muted-foreground">Sales</h3>
|
59
|
+
<% end %>
|
60
|
+
<% header.with_action do %>
|
61
|
+
<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">
|
62
|
+
<rect width="20" height="14" x="2" y="5" rx="2" />
|
63
|
+
<path d="M2 10h20" />
|
64
|
+
</svg>
|
65
|
+
<% end %>
|
66
|
+
<% end %>
|
67
|
+
<% card.with_body do %>
|
68
|
+
<div class="text-2xl font-bold">+12,234</div>
|
69
|
+
<p class="text-xs text-muted-foreground">+19% from last month</p>
|
70
|
+
<% end %>
|
71
|
+
<% end %>
|
72
|
+
|
73
|
+
<!-- Active Now Card -->
|
74
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
75
|
+
<% card.with_header(class: "!pb-2") do |header| %>
|
76
|
+
<% header.with_title do %>
|
77
|
+
<h3 class="text-sm font-medium text-muted-foreground">Active Now</h3>
|
78
|
+
<% end %>
|
79
|
+
<% header.with_action do %>
|
80
|
+
<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">
|
81
|
+
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
82
|
+
</svg>
|
83
|
+
<% end %>
|
84
|
+
<% end %>
|
85
|
+
<% card.with_body do %>
|
86
|
+
<div class="text-2xl font-bold">+573</div>
|
87
|
+
<p class="text-xs text-muted-foreground">+201 since last hour</p>
|
88
|
+
<% end %>
|
89
|
+
<% end %>
|
90
|
+
</div>
|
91
|
+
|
92
|
+
<!-- Chart and Recent Sales Section -->
|
93
|
+
<div class="grid gap-4 grid-cols-1 lg:grid-cols-7">
|
94
|
+
<!-- Overview Chart Card -->
|
95
|
+
<%= render M9sh::CardComponent.new(class: "lg:col-span-4") do |card| %>
|
96
|
+
<% card.with_header do |header| %>
|
97
|
+
<% header.with_title do %>
|
98
|
+
Overview
|
99
|
+
<% end %>
|
100
|
+
<% end %>
|
101
|
+
<% card.with_body do %>
|
102
|
+
<div class="h-[300px] flex items-center justify-center text-muted-foreground border-2 border-dashed rounded-lg">
|
103
|
+
Chart placeholder
|
104
|
+
</div>
|
105
|
+
<% end %>
|
106
|
+
<% end %>
|
107
|
+
|
108
|
+
<!-- Recent Sales Card -->
|
109
|
+
<%= render M9sh::CardComponent.new(class: "lg:col-span-3") do |card| %>
|
110
|
+
<% card.with_header do |header| %>
|
111
|
+
<% header.with_title do %>
|
112
|
+
Recent Sales
|
113
|
+
<% end %>
|
114
|
+
<% header.with_description do %>
|
115
|
+
You made 265 sales this month.
|
116
|
+
<% end %>
|
117
|
+
<% end %>
|
118
|
+
<% card.with_body do %>
|
119
|
+
<div class="space-y-6">
|
120
|
+
<% [
|
121
|
+
{ name: "Olivia Martin", email: "olivia.martin@email.com", amount: "+$1,999.00", avatar: "OM" },
|
122
|
+
{ name: "Jackson Lee", email: "jackson.lee@email.com", amount: "+$39.00", avatar: "JL" },
|
123
|
+
{ name: "Isabella Nguyen", email: "isabella.nguyen@email.com", amount: "+$299.00", avatar: "IN" },
|
124
|
+
{ name: "William Kim", email: "will@email.com", amount: "+$99.00", avatar: "WK" },
|
125
|
+
{ name: "Sofia Davis", email: "sofia.davis@email.com", amount: "+$39.00", avatar: "SD" }
|
126
|
+
].each do |sale| %>
|
127
|
+
<div class="flex items-center">
|
128
|
+
<%= render M9sh::AvatarComponent.new(fallback: sale[:avatar], class: "h-9 w-9") %>
|
129
|
+
<div class="ml-4 space-y-1">
|
130
|
+
<p class="text-sm font-medium leading-none"><%= sale[:name] %></p>
|
131
|
+
<p class="text-sm text-muted-foreground"><%= sale[:email] %></p>
|
132
|
+
</div>
|
133
|
+
<div class="ml-auto font-medium"><%= sale[:amount] %></div>
|
134
|
+
</div>
|
135
|
+
<% end %>
|
136
|
+
</div>
|
137
|
+
<% end %>
|
138
|
+
<% end %>
|
139
|
+
</div>
|
140
|
+
</div>
|
141
|
+
<% end %>
|
142
|
+
|
143
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
144
|
+
title: "Dashboard Example",
|
145
|
+
preview_content: dashboard_html,
|
146
|
+
code: dashboard_01_code,
|
147
|
+
ai_command: dashboard_01_code
|
148
|
+
) %>
|
149
|
+
|
150
|
+
<!-- Usage Section -->
|
151
|
+
<div class="mt-12 space-y-4">
|
152
|
+
<h2 class="text-2xl font-semibold tracking-tight">Usage</h2>
|
153
|
+
<p class="text-muted-foreground leading-relaxed">
|
154
|
+
Copy and paste the code above into your application. This dashboard uses the Card, Avatar, and Separator components from m9sh/ui.
|
155
|
+
</p>
|
156
|
+
|
157
|
+
<%= render M9sh::CardComponent.new(class: "mt-6") do |card| %>
|
158
|
+
<% card.with_header do |header| %>
|
159
|
+
<% header.with_title do %>
|
160
|
+
Required Components
|
161
|
+
<% end %>
|
162
|
+
<% header.with_description do %>
|
163
|
+
Make sure these components are installed in your project
|
164
|
+
<% end %>
|
165
|
+
<% end %>
|
166
|
+
<% card.with_body do %>
|
167
|
+
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
168
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
169
|
+
CardComponent
|
170
|
+
<% end %>
|
171
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
172
|
+
AvatarComponent
|
173
|
+
<% end %>
|
174
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
175
|
+
SeparatorComponent
|
176
|
+
<% end %>
|
177
|
+
</div>
|
178
|
+
<% end %>
|
179
|
+
<% end %>
|
180
|
+
</div>
|
@@ -0,0 +1,190 @@
|
|
1
|
+
<% content_for :title, "Dashboard 02 - Blocks" %>
|
2
|
+
|
3
|
+
<!-- Page Header -->
|
4
|
+
<div class="space-y-2 mb-8">
|
5
|
+
<h1 class="text-3xl font-bold tracking-tight">Dashboard 02</h1>
|
6
|
+
<p class="text-muted-foreground">
|
7
|
+
An analytics-focused dashboard with metrics, progress indicators, and activity feed. Ideal for SaaS applications and product analytics.
|
8
|
+
</p>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<!-- Dashboard Preview -->
|
12
|
+
<% dashboard_html = capture do %>
|
13
|
+
<div class="w-full space-y-6">
|
14
|
+
<!-- Top Metrics Row -->
|
15
|
+
<div class="grid gap-4 md:grid-cols-3">
|
16
|
+
<!-- Total Users Card -->
|
17
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
18
|
+
<% card.with_header do |header| %>
|
19
|
+
<% header.with_title do %>
|
20
|
+
Total Users
|
21
|
+
<% end %>
|
22
|
+
<% header.with_description do %>
|
23
|
+
Active users this month
|
24
|
+
<% end %>
|
25
|
+
<% end %>
|
26
|
+
<% card.with_body do %>
|
27
|
+
<div class="text-3xl font-bold">12,543</div>
|
28
|
+
<p class="text-xs text-muted-foreground mt-2">
|
29
|
+
<%= render M9sh::BadgeComponent.new(variant: :default, class: "text-xs") do %>
|
30
|
+
+12.5%
|
31
|
+
<% end %>
|
32
|
+
from last month
|
33
|
+
</p>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
36
|
+
|
37
|
+
<!-- Conversion Rate Card -->
|
38
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
39
|
+
<% card.with_header do |header| %>
|
40
|
+
<% header.with_title do %>
|
41
|
+
Conversion Rate
|
42
|
+
<% end %>
|
43
|
+
<% header.with_description do %>
|
44
|
+
Trial to paid conversion
|
45
|
+
<% end %>
|
46
|
+
<% end %>
|
47
|
+
<% card.with_body do %>
|
48
|
+
<div class="text-3xl font-bold">24.8%</div>
|
49
|
+
<%= render M9sh::ProgressComponent.new(value: 24.8, class: "mt-2") %>
|
50
|
+
<% end %>
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
<!-- Average Revenue Card -->
|
54
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
55
|
+
<% card.with_header do |header| %>
|
56
|
+
<% header.with_title do %>
|
57
|
+
Avg Revenue
|
58
|
+
<% end %>
|
59
|
+
<% header.with_description do %>
|
60
|
+
Per user this month
|
61
|
+
<% end %>
|
62
|
+
<% end %>
|
63
|
+
<% card.with_body do %>
|
64
|
+
<div class="text-3xl font-bold">$89.42</div>
|
65
|
+
<p class="text-xs text-muted-foreground mt-2">
|
66
|
+
<%= render M9sh::BadgeComponent.new(variant: :destructive, class: "text-xs") do %>
|
67
|
+
-3.2%
|
68
|
+
<% end %>
|
69
|
+
from last month
|
70
|
+
</p>
|
71
|
+
<% end %>
|
72
|
+
<% end %>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
<!-- Main Content Grid -->
|
76
|
+
<div class="grid gap-4 grid-cols-1 lg:grid-cols-2">
|
77
|
+
<!-- Activity Feed Card -->
|
78
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
79
|
+
<% card.with_header do |header| %>
|
80
|
+
<% header.with_title do %>
|
81
|
+
Recent Activity
|
82
|
+
<% end %>
|
83
|
+
<% header.with_description do %>
|
84
|
+
Latest user actions
|
85
|
+
<% end %>
|
86
|
+
<% end %>
|
87
|
+
<% card.with_body do %>
|
88
|
+
<div class="space-y-4">
|
89
|
+
<% [
|
90
|
+
{ user: "Alex Johnson", action: "created a new project", time: "2 minutes ago", badge: "New" },
|
91
|
+
{ user: "Sarah Chen", action: "upgraded to Pro plan", time: "15 minutes ago", badge: "Upgrade" },
|
92
|
+
{ user: "Michael Brown", action: "invited 3 team members", time: "1 hour ago", badge: "Team" },
|
93
|
+
{ user: "Emma Wilson", action: "completed onboarding", time: "2 hours ago", badge: "New" },
|
94
|
+
{ user: "David Lee", action: "exported data report", time: "3 hours ago", badge: "Export" }
|
95
|
+
].each do |activity| %>
|
96
|
+
<div class="flex items-start gap-4 pb-4 border-b border-border last:border-0 last:pb-0">
|
97
|
+
<%= render M9sh::AvatarComponent.new(fallback: activity[:user].split.map(&:first).join, class: "h-8 w-8") %>
|
98
|
+
<div class="flex-1 space-y-1">
|
99
|
+
<p class="text-sm">
|
100
|
+
<span class="font-medium"><%= activity[:user] %></span> <%= activity[:action] %>
|
101
|
+
</p>
|
102
|
+
<p class="text-xs text-muted-foreground"><%= activity[:time] %></p>
|
103
|
+
</div>
|
104
|
+
<%= render M9sh::BadgeComponent.new(variant: :outline, class: "text-xs") do %>
|
105
|
+
<%= activity[:badge] %>
|
106
|
+
<% end %>
|
107
|
+
</div>
|
108
|
+
<% end %>
|
109
|
+
</div>
|
110
|
+
<% end %>
|
111
|
+
<% end %>
|
112
|
+
|
113
|
+
<!-- Goals Progress Card -->
|
114
|
+
<%= render M9sh::CardComponent.new do |card| %>
|
115
|
+
<% card.with_header do |header| %>
|
116
|
+
<% header.with_title do %>
|
117
|
+
Monthly Goals
|
118
|
+
<% end %>
|
119
|
+
<% header.with_description do %>
|
120
|
+
Track your progress
|
121
|
+
<% end %>
|
122
|
+
<% end %>
|
123
|
+
<% card.with_body do %>
|
124
|
+
<div class="space-y-6">
|
125
|
+
<% [
|
126
|
+
{ goal: "New Signups", current: 842, target: 1000, percentage: 84.2 },
|
127
|
+
{ goal: "Revenue Target", current: 45231, target: 50000, percentage: 90.5 },
|
128
|
+
{ goal: "Active Users", current: 6543, target: 8000, percentage: 81.8 },
|
129
|
+
{ goal: "Support Tickets", current: 23, target: 50, percentage: 46 }
|
130
|
+
].each do |goal| %>
|
131
|
+
<div class="space-y-2">
|
132
|
+
<div class="flex items-center justify-between text-sm">
|
133
|
+
<span class="font-medium"><%= goal[:goal] %></span>
|
134
|
+
<span class="text-muted-foreground">
|
135
|
+
<%= goal[:current].to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse %> /
|
136
|
+
<%= goal[:target].to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse %>
|
137
|
+
</span>
|
138
|
+
</div>
|
139
|
+
<%= render M9sh::ProgressComponent.new(value: goal[:percentage]) %>
|
140
|
+
<p class="text-xs text-muted-foreground text-right"><%= goal[:percentage] %>%</p>
|
141
|
+
</div>
|
142
|
+
<% end %>
|
143
|
+
</div>
|
144
|
+
<% end %>
|
145
|
+
<% end %>
|
146
|
+
</div>
|
147
|
+
</div>
|
148
|
+
<% end %>
|
149
|
+
|
150
|
+
<%= render Docs::ComponentPreviewComponent.new(
|
151
|
+
title: "Analytics Dashboard",
|
152
|
+
preview_content: dashboard_html,
|
153
|
+
code: dashboard_02_code,
|
154
|
+
ai_command: dashboard_02_code
|
155
|
+
) %>
|
156
|
+
|
157
|
+
<!-- Usage Section -->
|
158
|
+
<div class="mt-12 space-y-4">
|
159
|
+
<h2 class="text-2xl font-semibold tracking-tight">Usage</h2>
|
160
|
+
<p class="text-muted-foreground leading-relaxed">
|
161
|
+
An analytics dashboard showing key metrics, activity feeds, and goal tracking. Perfect for SaaS applications and product analytics platforms.
|
162
|
+
</p>
|
163
|
+
|
164
|
+
<%= render M9sh::CardComponent.new(class: "mt-6") do |card| %>
|
165
|
+
<% card.with_header do |header| %>
|
166
|
+
<% header.with_title do %>
|
167
|
+
Required Components
|
168
|
+
<% end %>
|
169
|
+
<% header.with_description do %>
|
170
|
+
Make sure these components are installed in your project
|
171
|
+
<% end %>
|
172
|
+
<% end %>
|
173
|
+
<% card.with_body do %>
|
174
|
+
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
175
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
176
|
+
CardComponent
|
177
|
+
<% end %>
|
178
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
179
|
+
AvatarComponent
|
180
|
+
<% end %>
|
181
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
182
|
+
BadgeComponent
|
183
|
+
<% end %>
|
184
|
+
<%= render M9sh::BadgeComponent.new(variant: :secondary) do %>
|
185
|
+
ProgressComponent
|
186
|
+
<% end %>
|
187
|
+
</div>
|
188
|
+
<% end %>
|
189
|
+
<% end %>
|
190
|
+
</div>
|