m9sh 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.idea/hotcdn.iml +30 -0
  3. data/.mise.toml +2 -2
  4. data/app/assets/config/manifest.js +4 -0
  5. data/app/assets/images/icons/activity.svg +3 -0
  6. data/app/assets/images/icons/bell.svg +4 -0
  7. data/app/assets/images/icons/book.svg +4 -0
  8. data/app/assets/images/icons/chevron-down.svg +3 -0
  9. data/app/assets/images/icons/chevron-left.svg +3 -0
  10. data/app/assets/images/icons/chevron-right.svg +3 -0
  11. data/app/assets/images/icons/credit-card.svg +4 -0
  12. data/app/assets/images/icons/dollar-sign.svg +3 -0
  13. data/app/assets/images/icons/edit.svg +4 -0
  14. data/app/assets/images/icons/github.svg +3 -0
  15. data/app/assets/images/icons/home.svg +4 -0
  16. data/app/assets/images/icons/info.svg +5 -0
  17. data/app/assets/images/icons/layout.svg +6 -0
  18. data/app/assets/images/icons/logout.svg +5 -0
  19. data/app/assets/images/icons/menu.svg +5 -0
  20. data/app/assets/images/icons/moon.svg +3 -0
  21. data/app/assets/images/icons/paintbrush.svg +6 -0
  22. data/app/assets/images/icons/search.svg +4 -0
  23. data/app/assets/images/icons/settings.svg +4 -0
  24. data/app/assets/images/icons/sun.svg +11 -0
  25. data/app/assets/images/icons/user.svg +4 -0
  26. data/app/assets/images/icons/users.svg +5 -0
  27. data/app/assets/stylesheets/tailwind.css +1180 -0
  28. data/app/components/backdrop_component.rb +103 -0
  29. data/app/components/docs/code_block_component.rb +56 -0
  30. data/app/components/docs/component_api_component.rb +16 -0
  31. data/app/components/docs/component_examples_component.rb +16 -0
  32. data/app/components/docs/component_header_component.html.erb +8 -0
  33. data/app/components/docs/component_header_component.rb +14 -0
  34. data/app/components/docs/component_installation_component.html.erb +15 -0
  35. data/app/components/docs/component_installation_component.rb +13 -0
  36. data/app/components/docs/component_page_component.html.erb +9 -0
  37. data/app/components/docs/component_page_component.rb +19 -0
  38. data/app/components/docs/component_preview_component.rb +318 -0
  39. data/app/components/docs/component_usage_component.rb +18 -0
  40. data/app/components/docs/prop_table_component.rb +64 -0
  41. data/app/controllers/application_controller.rb +3 -0
  42. data/app/controllers/blocks_controller.rb +51 -0
  43. data/app/controllers/docs_controller.rb +162 -0
  44. data/app/controllers/showcase_controller.rb +42 -0
  45. data/app/helpers/blocks_helper.rb +343 -0
  46. data/app/helpers/docs_helper.rb +3807 -0
  47. data/app/helpers/m9sh/toast_helper.rb +46 -0
  48. data/app/helpers/m9sh_helper.rb +343 -0
  49. data/app/javascript/application.js +3 -0
  50. data/app/javascript/controllers/application.js +9 -0
  51. data/app/javascript/controllers/backdrop_controller.js +137 -0
  52. data/app/javascript/controllers/color_customizer_controller.js +569 -0
  53. data/app/javascript/controllers/color_theme_controller.js +120 -0
  54. data/app/javascript/controllers/docs/component_preview_controller.js +149 -0
  55. data/app/javascript/controllers/docs/copy_button_controller.js +20 -0
  56. data/app/javascript/controllers/index.js +6 -0
  57. data/app/javascript/controllers/theme_controller.js +23 -0
  58. data/app/views/blocks/_sidebar.html.erb +31 -0
  59. data/app/views/blocks/_toc.html.erb +29 -0
  60. data/app/views/blocks/examples/dashboard-01.html.erb +180 -0
  61. data/app/views/blocks/examples/dashboard-02.html.erb +190 -0
  62. data/app/views/blocks/examples/dashboard-03.html.erb +210 -0
  63. data/app/views/blocks/examples/settings-01.html.erb +220 -0
  64. data/app/views/blocks/examples/settings-02.html.erb +231 -0
  65. data/app/views/blocks/examples/settings-03.html.erb +340 -0
  66. data/app/views/blocks/index.html.erb +65 -0
  67. data/app/views/docs/_sidebar.html.erb +47 -0
  68. data/app/views/docs/_toc.html.erb +19 -0
  69. data/app/views/docs/about.html.erb +68 -0
  70. data/app/views/docs/components/accordion.html.erb +196 -0
  71. data/app/views/docs/components/alert.html.erb +272 -0
  72. data/app/views/docs/components/alert_dialog.html.erb +232 -0
  73. data/app/views/docs/components/avatar.html.erb +207 -0
  74. data/app/views/docs/components/badge.html.erb +145 -0
  75. data/app/views/docs/components/breadcrumb.html.erb +264 -0
  76. data/app/views/docs/components/button.html.erb +229 -0
  77. data/app/views/docs/components/card.html.erb +378 -0
  78. data/app/views/docs/components/checkbox.html.erb +212 -0
  79. data/app/views/docs/components/collapsible.html.erb +252 -0
  80. data/app/views/docs/components/dialog.html.erb +323 -0
  81. data/app/views/docs/components/dropdown_menu.html.erb +289 -0
  82. data/app/views/docs/components/hover_card.html.erb +220 -0
  83. data/app/views/docs/components/input.html.erb +254 -0
  84. data/app/views/docs/components/label.html.erb +128 -0
  85. data/app/views/docs/components/main.html.erb +352 -0
  86. data/app/views/docs/components/navbar.html.erb +394 -0
  87. data/app/views/docs/components/navigation_menu.html.erb +226 -0
  88. data/app/views/docs/components/popover.html.erb +267 -0
  89. data/app/views/docs/components/progress.html.erb +107 -0
  90. data/app/views/docs/components/radio_group.html.erb +209 -0
  91. data/app/views/docs/components/select.html.erb +260 -0
  92. data/app/views/docs/components/separator.html.erb +162 -0
  93. data/app/views/docs/components/sheet.html.erb +270 -0
  94. data/app/views/docs/components/sidebar.html.erb +597 -0
  95. data/app/views/docs/components/skeleton.html.erb +150 -0
  96. data/app/views/docs/components/slider.html.erb +218 -0
  97. data/app/views/docs/components/spinner.html.erb +132 -0
  98. data/app/views/docs/components/switch.html.erb +148 -0
  99. data/app/views/docs/components/table.html.erb +259 -0
  100. data/app/views/docs/components/tabs.html.erb +225 -0
  101. data/app/views/docs/components/textarea.html.erb +239 -0
  102. data/app/views/docs/components/theme_toggle.html.erb +135 -0
  103. data/app/views/docs/components/toast.html.erb +205 -0
  104. data/app/views/docs/components/toaster.html.erb +227 -0
  105. data/app/views/docs/components/toggle.html.erb +154 -0
  106. data/app/views/docs/components/tooltip.html.erb +216 -0
  107. data/app/views/docs/components/typography.html.erb +180 -0
  108. data/app/views/docs/index.html.erb +143 -0
  109. data/app/views/docs/installation.html.erb +155 -0
  110. data/app/views/docs/simple_test.html.erb +13 -0
  111. data/app/views/docs/test_accordion.html.erb +14 -0
  112. data/app/views/docs/usage.html.erb +272 -0
  113. data/app/views/layouts/application.html.erb +107 -0
  114. data/app/views/layouts/backdrop.html.erb +77 -0
  115. data/app/views/shared/_app_navbar.html.erb +240 -0
  116. data/app/views/shared/_navbar.html.erb +69 -0
  117. data/app/views/showcase/v2/_components_grid.html.erb +38 -0
  118. data/app/views/showcase/v2/_features.html.erb +59 -0
  119. data/app/views/showcase/v2/_forms.html.erb +195 -0
  120. data/app/views/showcase/v2/_hero.html.erb +55 -0
  121. data/app/views/showcase/v2/_metrics.html.erb +107 -0
  122. data/app/views/showcase/v2.html.erb +18 -0
  123. data/lib/m9sh/version.rb +1 -1
  124. data/m9sh.gemspec +1 -1
  125. metadata +120 -1
@@ -0,0 +1,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>