maquina-components 0.5.0 → 0.5.1

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/app/assets/stylesheets/breadcrumbs.css +21 -0
  4. data/app/assets/stylesheets/combobox.css +36 -1
  5. data/app/assets/stylesheets/drawer.css +15 -0
  6. data/app/assets/stylesheets/menu_button.css +155 -0
  7. data/app/assets/stylesheets/separator.css +20 -0
  8. data/app/assets/stylesheets/sidebar.css +12 -0
  9. data/app/assets/stylesheets/stats.css +101 -0
  10. data/app/assets/tailwind/maquina_components_engine/engine.css +3 -0
  11. data/app/helpers/maquina_components/combobox_helper.rb +2 -1
  12. data/app/helpers/maquina_components/components_helper.rb +33 -0
  13. data/app/helpers/maquina_components/icons_helper.rb +58 -0
  14. data/app/javascript/controllers/breadcrumb_controller.js +39 -30
  15. data/app/javascript/controllers/combobox_controller.js +9 -0
  16. data/app/javascript/controllers/drawer_controller.js +43 -9
  17. data/app/javascript/controllers/drawer_trigger_controller.js +33 -3
  18. data/app/javascript/controllers/menu_button_controller.js +107 -29
  19. data/app/javascript/controllers/sidebar_controller.js +6 -8
  20. data/app/javascript/controllers/sidebar_trigger_controller.js +33 -4
  21. data/app/javascript/controllers/toaster_controller.js +12 -0
  22. data/app/views/components/_alert.html.erb +3 -1
  23. data/app/views/components/_badge.html.erb +7 -1
  24. data/app/views/components/_breadcrumbs.html.erb +1 -1
  25. data/app/views/components/_calendar.html.erb +1 -1
  26. data/app/views/components/_card.html.erb +1 -1
  27. data/app/views/components/_combobox.html.erb +3 -2
  28. data/app/views/components/_date_picker.html.erb +4 -3
  29. data/app/views/components/_drawer.html.erb +8 -2
  30. data/app/views/components/_dropdown.html.erb +21 -24
  31. data/app/views/components/_dropdown_menu.html.erb +1 -1
  32. data/app/views/components/_empty.html.erb +1 -1
  33. data/app/views/components/_header.html.erb +1 -1
  34. data/app/views/components/_menu_button.html.erb +29 -34
  35. data/app/views/components/_pagination.html.erb +1 -1
  36. data/app/views/components/_separator.html.erb +9 -10
  37. data/app/views/components/_sidebar.html.erb +1 -1
  38. data/app/views/components/_simple_table.html.erb +3 -1
  39. data/app/views/components/_table.html.erb +1 -1
  40. data/app/views/components/_toast.html.erb +6 -1
  41. data/app/views/components/_toaster.html.erb +1 -1
  42. data/app/views/components/_toggle_group.html.erb +1 -1
  43. data/app/views/components/alert/_description.html.erb +1 -1
  44. data/app/views/components/alert/_title.html.erb +1 -1
  45. data/app/views/components/breadcrumbs/_ellipsis.html.erb +1 -1
  46. data/app/views/components/breadcrumbs/_item.html.erb +1 -1
  47. data/app/views/components/breadcrumbs/_link.html.erb +1 -1
  48. data/app/views/components/breadcrumbs/_list.html.erb +1 -1
  49. data/app/views/components/breadcrumbs/_page.html.erb +1 -1
  50. data/app/views/components/breadcrumbs/_separator.html.erb +1 -1
  51. data/app/views/components/calendar/_header.html.erb +1 -1
  52. data/app/views/components/calendar/_week.html.erb +1 -1
  53. data/app/views/components/card/_action.html.erb +1 -1
  54. data/app/views/components/card/_content.html.erb +1 -1
  55. data/app/views/components/card/_description.html.erb +1 -1
  56. data/app/views/components/card/_footer.html.erb +1 -1
  57. data/app/views/components/card/_header.html.erb +1 -1
  58. data/app/views/components/card/_title.html.erb +1 -1
  59. data/app/views/components/combobox/_content.html.erb +1 -1
  60. data/app/views/components/combobox/_empty.html.erb +3 -3
  61. data/app/views/components/combobox/_group.html.erb +1 -1
  62. data/app/views/components/combobox/_input.html.erb +1 -1
  63. data/app/views/components/combobox/_label.html.erb +1 -1
  64. data/app/views/components/combobox/_list.html.erb +1 -1
  65. data/app/views/components/combobox/_option.html.erb +1 -1
  66. data/app/views/components/combobox/_separator.html.erb +1 -1
  67. data/app/views/components/combobox/_trigger.html.erb +1 -1
  68. data/app/views/components/drawer/_close.html.erb +1 -1
  69. data/app/views/components/drawer/_content.html.erb +1 -1
  70. data/app/views/components/drawer/_footer.html.erb +1 -1
  71. data/app/views/components/drawer/_header.html.erb +2 -2
  72. data/app/views/components/drawer/_provider.html.erb +2 -2
  73. data/app/views/components/drawer/_trigger.html.erb +1 -1
  74. data/app/views/components/dropdown_menu/_content.html.erb +1 -1
  75. data/app/views/components/dropdown_menu/_group.html.erb +1 -1
  76. data/app/views/components/dropdown_menu/_item.html.erb +1 -1
  77. data/app/views/components/dropdown_menu/_label.html.erb +3 -7
  78. data/app/views/components/dropdown_menu/_separator.html.erb +1 -1
  79. data/app/views/components/dropdown_menu/_shortcut.html.erb +3 -7
  80. data/app/views/components/dropdown_menu/_trigger.html.erb +1 -1
  81. data/app/views/components/empty/_content.html.erb +1 -1
  82. data/app/views/components/empty/_description.html.erb +3 -7
  83. data/app/views/components/empty/_header.html.erb +1 -1
  84. data/app/views/components/empty/_media.html.erb +5 -3
  85. data/app/views/components/empty/_title.html.erb +3 -7
  86. data/app/views/components/pagination/_content.html.erb +1 -1
  87. data/app/views/components/pagination/_item.html.erb +1 -1
  88. data/app/views/components/pagination/_link.html.erb +1 -1
  89. data/app/views/components/pagination/_next.html.erb +1 -1
  90. data/app/views/components/pagination/_previous.html.erb +1 -1
  91. data/app/views/components/sidebar/_content.html.erb +1 -1
  92. data/app/views/components/sidebar/_footer.html.erb +1 -1
  93. data/app/views/components/sidebar/_group.html.erb +1 -1
  94. data/app/views/components/sidebar/_header.html.erb +1 -1
  95. data/app/views/components/sidebar/_inset.html.erb +1 -1
  96. data/app/views/components/sidebar/_menu.html.erb +1 -1
  97. data/app/views/components/sidebar/_menu_button.html.erb +1 -1
  98. data/app/views/components/sidebar/_menu_item.html.erb +1 -1
  99. data/app/views/components/sidebar/_menu_link.html.erb +4 -7
  100. data/app/views/components/sidebar/_provider.html.erb +1 -1
  101. data/app/views/components/sidebar/_trigger.html.erb +1 -1
  102. data/app/views/components/stats/_stats_card.html.erb +32 -92
  103. data/app/views/components/stats/_stats_grid.html.erb +18 -24
  104. data/app/views/components/table/_body.html.erb +1 -1
  105. data/app/views/components/table/_caption.html.erb +3 -3
  106. data/app/views/components/table/_cell.html.erb +3 -3
  107. data/app/views/components/table/_footer.html.erb +1 -1
  108. data/app/views/components/table/_head.html.erb +3 -3
  109. data/app/views/components/table/_header.html.erb +1 -1
  110. data/app/views/components/table/_row.html.erb +1 -1
  111. data/app/views/components/toast/_action.html.erb +1 -1
  112. data/app/views/components/toast/_description.html.erb +1 -1
  113. data/app/views/components/toast/_title.html.erb +1 -1
  114. data/app/views/components/toggle_group/_item.html.erb +1 -1
  115. data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +1 -0
  116. data/lib/maquina-components.rb +5 -0
  117. data/lib/maquina_components/engine.rb +7 -3
  118. data/lib/maquina_components/version.rb +1 -1
  119. metadata +33 -2
  120. data/app/assets/stylesheets/maquina_components.css +0 -178
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5288e1d8b1501f99a2b94c8d296e4babbe5e1d9d42f41d6f969dc1742e78c48
4
- data.tar.gz: f4b31f8e38c025b05fc9a12a8c16d13b1fc62cfee10ce4596711a935203c39c4
3
+ metadata.gz: 9159362a034d89b9abf1b6a5a636851942f69c32491eea74c438c9d8dc678b57
4
+ data.tar.gz: 9ab23823a833fdfb8d02727fea268539ea0dd26d974b3212aedf90c87d2c5963
5
5
  SHA512:
6
- metadata.gz: 0c9977c27048fb814c4158033afa8bcfd538a257531d94cfe07a9f35dc623dda73503d430fea93947f026caf55ab9b8237423a010af0677f2d637b3abebd6138
7
- data.tar.gz: 51c6744241d753e6b022c481fead7ad3b47725ac52cddf43a655f78a94c066f69b412a6f4f347a93c8e6935221ba743a6807035ebd86c1ffdd036605a6c30b80
6
+ metadata.gz: 2b7d4230c490e4a98db049a2a527300b060873b6befb6925c2825455923e2179f353ffadbca0f90ebda98779ef711083b819c60a13763a5d982be3d22b73ab4e
7
+ data.tar.gz: c0671e469d43f1fbc265638755f453365748f2cf3d39e435ffa5218d81f9453a55f87cd1babe8acdfb2ccfdfb21a4e2415366a82388ddabb74f9a127a3bdc821
data/README.md CHANGED
@@ -152,8 +152,10 @@ generates views built with maquina_components. Customize the templates as needed
152
152
  | **Card** | Content container with header, content, footer | [Card](https://maquina.app/documentation/components/card/) |
153
153
  | **Alert** | Callout messages (info, warning, error) | [Alert](https://maquina.app/documentation/components/alert/) |
154
154
  | **Badge** | Status indicators and labels | [Badge](https://maquina.app/documentation/components/badge/) |
155
- | **Table** | Data tables with sorting support | [Table](https://maquina.app/documentation/components/table/) |
155
+ | **Table** | Data tables with striped and bordered variants | [Table](https://maquina.app/documentation/components/table/) |
156
+ | **Separator** | Horizontal or vertical divider | [Separator](https://maquina.app/documentation/components/separator/) |
156
157
  | **Empty State** | Placeholder for empty lists | [Empty State](https://maquina.app/documentation/components/empty/) |
158
+ | **Stats** | Metric cards in a responsive grid | [Stats](https://maquina.app/documentation/components/stats/) |
157
159
 
158
160
  ### Navigation Components
159
161
 
@@ -94,6 +94,7 @@
94
94
  [data-breadcrumb-part="ellipsis"] {
95
95
  @apply inline-flex size-9 items-center justify-center;
96
96
  color: var(--muted-foreground);
97
+ cursor: pointer;
97
98
  }
98
99
 
99
100
  [data-breadcrumb-part="ellipsis"] svg {
@@ -106,6 +107,26 @@
106
107
  color: var(--foreground);
107
108
  }
108
109
 
110
+ /* ===== Overflow Dropdown (top-layer popover) ===== */
111
+ /* Visual styling comes from dropdown_menu.css via data-dropdown-menu-part.
112
+ Anchor positioning places the popover in modern browsers;
113
+ breadcrumb_controller falls back to measured coordinates elsewhere.
114
+ The doubled attribute selector outranks dropdown_menu.css's
115
+ position: absolute regardless of import order. */
116
+ @supports (anchor-name: --a) and (position-area: block-end) {
117
+ [data-breadcrumb-part="ellipsis"] {
118
+ anchor-name: --maquina-breadcrumbs;
119
+ }
120
+
121
+ [data-breadcrumb-part="dropdown"][data-dropdown-menu-part="content"] {
122
+ position: fixed;
123
+ position-anchor: --maquina-breadcrumbs;
124
+ position-area: block-end span-inline-end;
125
+ margin-block-start: 4px;
126
+ position-try-fallbacks: flip-block;
127
+ }
128
+ }
129
+
109
130
  /* ===== Responsive Behavior ===== */
110
131
  /* Force nowrap when responsive controller is active so scrollWidth detects overflow */
111
132
  [data-controller="breadcrumb"] [data-breadcrumb-part="list"] {
@@ -53,12 +53,47 @@
53
53
  @apply rounded-md border p-0 shadow-md outline-none;
54
54
  @apply min-w-[200px];
55
55
 
56
- /* Reset popover defaults - positioning handled by JS */
56
+ /* Reset popover defaults - positioning below (anchor CSS) or in JS fallback */
57
57
  margin: 0;
58
58
  padding: 0;
59
59
  inset: unset;
60
60
  }
61
61
 
62
+ /* ===== Anchor Positioning (modern browsers) ===== */
63
+ /* Places the top-layer popover declaratively; combobox_controller skips its
64
+ measured fallback when these features are supported. flip-block flips the
65
+ popover above the trigger when there is no room below — something the JS
66
+ fallback never did. */
67
+ @supports (anchor-name: --a) and (position-area: block-end) {
68
+ [data-combobox-part="trigger"] {
69
+ anchor-name: --maquina-combobox;
70
+ }
71
+
72
+ [data-combobox-part="content"] {
73
+ position: fixed;
74
+ position-anchor: --maquina-combobox;
75
+ margin-block-start: 4px;
76
+ position-try-fallbacks: flip-block;
77
+ }
78
+
79
+ [data-combobox-part="content"][data-align="start"] {
80
+ position-area: block-end span-inline-end;
81
+ }
82
+
83
+ [data-combobox-part="content"][data-align="center"] {
84
+ position-area: block-end;
85
+ }
86
+
87
+ [data-combobox-part="content"][data-align="end"] {
88
+ position-area: block-end span-inline-start;
89
+ }
90
+
91
+ [data-combobox-part="content"][data-width="default"] {
92
+ width: auto;
93
+ min-width: max(200px, anchor-size(inline));
94
+ }
95
+ }
96
+
62
97
  /* Width variants */
63
98
  [data-combobox-part="content"][data-width="sm"] {
64
99
  @apply w-40;
@@ -57,12 +57,27 @@
57
57
  @apply flex h-full w-full flex-col;
58
58
  }
59
59
 
60
+ /* ===== Body Scroll Lock ===== */
61
+ /* Shared by drawer_controller and sidebar_controller: the controllers set
62
+ the data attribute and the --maquina-scroll-lock-top offset on <body>;
63
+ all styling lives here. */
64
+ body[data-maquina-scroll-locked] {
65
+ position: fixed;
66
+ top: var(--maquina-scroll-lock-top, 0px);
67
+ width: 100%;
68
+ overflow: hidden;
69
+ }
70
+
60
71
  /* ===== Drawer Header ===== */
61
72
  [data-drawer-part="header"] {
62
73
  @apply flex shrink-0 items-center justify-between gap-2 border-b px-4 py-3;
63
74
  border-color: var(--border);
64
75
  }
65
76
 
77
+ [data-drawer-part="header-content"] {
78
+ @apply flex-1;
79
+ }
80
+
66
81
  /* ===== Drawer Content (scrollable area) ===== */
67
82
  [data-drawer-part="content"] {
68
83
  @apply flex min-h-0 flex-1 flex-col gap-2 overflow-auto p-4;
@@ -0,0 +1,155 @@
1
+ /* ===== Menu Button Component Styles ===== */
2
+ /*
3
+ * Sidebar-flavored button that opens a dropdown panel.
4
+ * Pairs _menu_button.html.erb (trigger) with _dropdown.html.erb (content)
5
+ * and menu_button_controller.js.
6
+ *
7
+ * Structure:
8
+ * - menu-button (root ul)
9
+ * - item (li with Stimulus controller)
10
+ * - trigger (button)
11
+ * - avatar (text icon square)
12
+ * - text (title + subtitle)
13
+ * - content (positioned dropdown panel)
14
+ */
15
+
16
+ /* ===== Root Container ===== */
17
+ [data-component="menu-button"] {
18
+ @apply flex w-full min-w-0 flex-col gap-1;
19
+ margin: 0;
20
+ padding: 0;
21
+ list-style: none;
22
+ }
23
+
24
+ [data-menu-button-part="item"] {
25
+ position: relative;
26
+ list-style: none;
27
+ }
28
+
29
+ /* ===== Trigger Button ===== */
30
+ [data-menu-button-part="trigger"] {
31
+ @apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm;
32
+ @apply transition-[width,height,padding] duration-150;
33
+ height: 3rem;
34
+ background-color: transparent;
35
+ color: inherit;
36
+ border: none;
37
+ outline: none;
38
+ cursor: pointer;
39
+ }
40
+
41
+ [data-menu-button-part="trigger"]:hover,
42
+ [data-menu-button-part="trigger"]:active,
43
+ [data-menu-button-part="trigger"][data-state="open"] {
44
+ background-color: var(--sidebar-accent);
45
+ color: var(--sidebar-accent-foreground);
46
+ }
47
+
48
+ [data-menu-button-part="trigger"]:focus-visible {
49
+ box-shadow: 0 0 0 2px var(--sidebar-ring);
50
+ }
51
+
52
+ [data-menu-button-part="trigger"]:disabled,
53
+ [data-menu-button-part="trigger"][aria-disabled="true"] {
54
+ pointer-events: none;
55
+ opacity: 0.5;
56
+ }
57
+
58
+ [data-menu-button-part="trigger"] svg {
59
+ @apply size-4 shrink-0;
60
+ }
61
+
62
+ [data-menu-button-part="trigger"] img {
63
+ @apply shrink-0;
64
+ }
65
+
66
+ /* Icon-collapsed sidebar squeezes the trigger to an icon square */
67
+ [data-sidebar-part="root"][data-collapsible="icon"] [data-menu-button-part="trigger"] {
68
+ width: 2rem;
69
+ height: 2rem;
70
+ padding: 0;
71
+ }
72
+
73
+ /* ===== Avatar (text icon) ===== */
74
+ [data-menu-button-part="avatar"] {
75
+ @apply flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg;
76
+ background-color: var(--sidebar-primary);
77
+ color: var(--sidebar-primary-foreground);
78
+ }
79
+
80
+ /* ===== Text (title + subtitle) ===== */
81
+ [data-menu-button-part="text"] {
82
+ @apply grid flex-1 text-left text-sm leading-tight;
83
+ }
84
+
85
+ [data-menu-button-part="title"] {
86
+ @apply truncate font-semibold;
87
+ }
88
+
89
+ [data-menu-button-part="subtitle"] {
90
+ @apply truncate text-xs;
91
+ }
92
+
93
+ /* ===== Content Panel ===== */
94
+ [data-menu-button-part="content"] {
95
+ position: absolute;
96
+ z-index: 50;
97
+ display: none;
98
+ flex-direction: column;
99
+ overflow: hidden;
100
+ min-width: 14rem;
101
+ max-width: 14rem;
102
+ background-color: var(--popover, var(--background));
103
+ color: var(--popover-foreground, var(--foreground));
104
+ @apply rounded-lg border p-1 shadow-md;
105
+ border-color: var(--border);
106
+ }
107
+
108
+ [data-menu-button-part="content"][data-side="top"] {
109
+ bottom: calc(100% + 4px);
110
+ }
111
+
112
+ [data-menu-button-part="content"][data-side="bottom"] {
113
+ top: calc(100% + 4px);
114
+ }
115
+
116
+ [data-menu-button-part="content"][data-align="start"] {
117
+ left: 0;
118
+ }
119
+
120
+ [data-menu-button-part="content"][data-align="end"] {
121
+ right: 0;
122
+ }
123
+
124
+ /* Animation states */
125
+ [data-menu-button-part="content"][data-state="open"] {
126
+ display: flex;
127
+ animation: menu-button-in 150ms ease-out;
128
+ }
129
+
130
+ [data-menu-button-part="content"][data-state="closing"] {
131
+ display: flex;
132
+ animation: menu-button-out 100ms ease-in forwards;
133
+ }
134
+
135
+ @keyframes menu-button-in {
136
+ from {
137
+ opacity: 0;
138
+ transform: scale(0.95) translateY(4px);
139
+ }
140
+ to {
141
+ opacity: 1;
142
+ transform: scale(1) translateY(0);
143
+ }
144
+ }
145
+
146
+ @keyframes menu-button-out {
147
+ from {
148
+ opacity: 1;
149
+ transform: scale(1) translateY(0);
150
+ }
151
+ to {
152
+ opacity: 0;
153
+ transform: scale(0.95) translateY(4px);
154
+ }
155
+ }
@@ -0,0 +1,20 @@
1
+ /* ===== Separator Component Styles ===== */
2
+ /*
3
+ * Decorative divider, horizontal or vertical.
4
+ * Styled via data attributes; colors come from theme variables.
5
+ */
6
+
7
+ [data-component="separator"] {
8
+ @apply mx-2 shrink-0;
9
+ background-color: var(--border);
10
+ }
11
+
12
+ [data-component="separator"][data-orientation="horizontal"] {
13
+ height: 1px;
14
+ width: 100%;
15
+ }
16
+
17
+ [data-component="separator"][data-orientation="vertical"] {
18
+ width: 1px;
19
+ height: 1rem;
20
+ }
@@ -290,6 +290,18 @@
290
290
  @apply truncate;
291
291
  }
292
292
 
293
+ [data-sidebar-part="menu-link-text"] {
294
+ @apply flex flex-col gap-0.5 leading-none;
295
+ }
296
+
297
+ [data-sidebar-part="menu-link-title"] {
298
+ @apply font-medium;
299
+ }
300
+
301
+ [data-sidebar-part="menu-link-subtitle"] {
302
+ @apply text-xs text-sidebar-foreground/70;
303
+ }
304
+
293
305
  /* Has menu action - add padding */
294
306
  .group-has-data-\[sidebar-part\=menu-action\]\/menu-item [data-sidebar-part="menu-link"],
295
307
  [data-sidebar-part="menu-item"]:has([data-sidebar-part="menu-action"]) [data-sidebar-part="menu-link"] {
@@ -0,0 +1,101 @@
1
+ /* ===== Stats Component Styles ===== */
2
+ /*
3
+ * Dashboard stat cards and their responsive grid.
4
+ *
5
+ * Structure:
6
+ * - stats-grid (root, optional action slot)
7
+ * - action (leading or trailing action area)
8
+ * - grid (responsive card grid, columns via data-columns)
9
+ * - stats-card
10
+ * - body
11
+ * - text (title, value, subtitle)
12
+ * - icon
13
+ */
14
+
15
+ /* ===== Card ===== */
16
+ [data-component="stats-card"] {
17
+ @apply rounded-lg border p-4 shadow-sm;
18
+ background-color: var(--card);
19
+ border-color: var(--border);
20
+ }
21
+
22
+ [data-component="stats-card"] [data-stats-part="body"] {
23
+ @apply flex items-center justify-between;
24
+ }
25
+
26
+ [data-component="stats-card"] [data-stats-part="title"] {
27
+ @apply text-sm;
28
+ color: var(--muted-foreground);
29
+ }
30
+
31
+ [data-component="stats-card"] [data-stats-part="value"] {
32
+ @apply text-2xl font-bold;
33
+ }
34
+
35
+ [data-component="stats-card"] [data-stats-part="subtitle"] {
36
+ @apply mt-1 text-xs;
37
+ color: var(--muted-foreground);
38
+ }
39
+
40
+ [data-component="stats-card"] [data-stats-part="icon"] svg {
41
+ @apply size-8 shrink-0;
42
+ }
43
+
44
+ /* Color defaults live in the components layer so caller utility classes
45
+ (value_classes / icon_classes, e.g. text-green-600) always win — the
46
+ utilities layer is defined to beat the components layer, and unlayered
47
+ rules would beat utilities regardless of specificity */
48
+ @layer components {
49
+ [data-component="stats-card"] [data-stats-part="value"] {
50
+ color: var(--foreground);
51
+ }
52
+
53
+ [data-component="stats-card"] [data-stats-part="icon"] {
54
+ color: var(--muted-foreground);
55
+ }
56
+ }
57
+
58
+ /* ===== Grid ===== */
59
+ [data-component="stats-grid"][data-with-action="true"] {
60
+ @apply flex flex-col gap-4 lg:flex-row lg:items-center;
61
+ }
62
+
63
+ [data-component="stats-grid"] [data-stats-part="grid"] {
64
+ @apply grid grid-cols-1 gap-4;
65
+ }
66
+
67
+ [data-component="stats-grid"][data-with-action="true"] [data-stats-part="grid"] {
68
+ @apply flex-1;
69
+ }
70
+
71
+ [data-component="stats-grid"][data-columns="2"] [data-stats-part="grid"] {
72
+ @apply sm:grid-cols-2;
73
+ }
74
+
75
+ [data-component="stats-grid"][data-columns="3"] [data-stats-part="grid"] {
76
+ @apply sm:grid-cols-3;
77
+ }
78
+
79
+ [data-component="stats-grid"][data-columns="4"] [data-stats-part="grid"] {
80
+ @apply sm:grid-cols-4;
81
+ }
82
+
83
+ [data-component="stats-grid"][data-columns="5"] [data-stats-part="grid"] {
84
+ @apply sm:grid-cols-5;
85
+ }
86
+
87
+ [data-component="stats-grid"][data-columns="6"] [data-stats-part="grid"] {
88
+ @apply sm:grid-cols-6;
89
+ }
90
+
91
+ [data-component="stats-grid"] [data-stats-part="action"] {
92
+ @apply flex justify-center;
93
+ }
94
+
95
+ [data-component="stats-grid"] [data-stats-part="action"][data-position="start"] {
96
+ @apply lg:justify-start;
97
+ }
98
+
99
+ [data-component="stats-grid"] [data-stats-part="action"][data-position="end"] {
100
+ @apply lg:justify-end;
101
+ }
@@ -12,8 +12,11 @@
12
12
  @import "../../stylesheets/empty.css";
13
13
  @import "../../stylesheets/form.css";
14
14
  @import "../../stylesheets/header.css";
15
+ @import "../../stylesheets/menu_button.css";
15
16
  @import "../../stylesheets/pagination.css";
17
+ @import "../../stylesheets/separator.css";
16
18
  @import "../../stylesheets/sidebar.css";
19
+ @import "../../stylesheets/stats.css";
17
20
  @import "../../stylesheets/table.css";
18
21
  @import "../../stylesheets/toast.css";
19
22
  @import "../../stylesheets/toggle_group.css";
@@ -59,7 +59,8 @@ module MaquinaComponents
59
59
  # @return [String] Rendered HTML
60
60
  def combobox(id: nil, name: nil, value: nil, placeholder: "Select...", css_classes: "", **html_options, &block)
61
61
  builder = ComboboxBuilder.new(self, placeholder: placeholder)
62
- combobox_id = id || "combobox-#{SecureRandom.hex(4)}"
62
+ # Deterministic fallback id: random ids break idiomorph matching across Turbo morphs
63
+ combobox_id = id || "combobox-#{name.to_s.parameterize.presence || "default"}"
63
64
  builder.combobox_id = combobox_id
64
65
 
65
66
  capture(builder, &block)
@@ -0,0 +1,33 @@
1
+ module MaquinaComponents
2
+ # Shared plumbing for component partials.
3
+ module ComponentsHelper
4
+ # Merges a caller's data: hash with a component's own data attributes.
5
+ #
6
+ # Component values win for identity keys (component, variant, size, ...)
7
+ # so callers can't accidentally break a component's styling hooks. The
8
+ # Stimulus token keys :controller and :action concatenate instead, so a
9
+ # caller can attach extra behavior without losing the component's:
10
+ #
11
+ # <%= render "components/combobox", name: "country",
12
+ # data: { controller: "analytics", action: "change->analytics#track" } %>
13
+ # # => data-controller="combobox analytics"
14
+ #
15
+ # @param html_options [Hash] the partial's **html_options (data: is consumed)
16
+ # @param component_data [Hash] the component's own data attributes
17
+ # @return [Hash] the merged data hash
18
+ def merge_component_data(html_options, **component_data)
19
+ user_data = html_options.delete(:data) || {}
20
+ merged = user_data.merge(component_data)
21
+
22
+ [:controller, :action].each do |key|
23
+ user_value = user_data[key] || user_data[key.to_s]
24
+ next if user_value.blank? || component_data[key].blank?
25
+
26
+ merged.delete(key.to_s)
27
+ merged[key] = "#{component_data[key]} #{user_value}"
28
+ end
29
+
30
+ merged
31
+ end
32
+ end
33
+ end
@@ -401,6 +401,64 @@ module MaquinaComponents
401
401
  <rect width="7" height="7" x="3" y="14" rx="1"/>
402
402
  </svg>
403
403
  SVG
404
+ when :message_square
405
+ <<~SVG.freeze
406
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
407
+ <path d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
408
+ </svg>
409
+ SVG
410
+ when :arrow_up
411
+ <<~SVG.freeze
412
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
413
+ <path d="M7 17L17 7M17 7H7M17 7V17"/>
414
+ </svg>
415
+ SVG
416
+ when :arrow_down
417
+ <<~SVG.freeze
418
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
419
+ <path d="M17 7L7 17M7 17H17M7 17V7"/>
420
+ </svg>
421
+ SVG
422
+ when :lightning_bolt
423
+ <<~SVG.freeze
424
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
425
+ <path d="M13 10V3L4 14h7v7l9-11h-7z"/>
426
+ </svg>
427
+ SVG
428
+ when :clipboard_list
429
+ <<~SVG.freeze
430
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
431
+ <path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
432
+ </svg>
433
+ SVG
434
+ when :chart_bar
435
+ <<~SVG.freeze
436
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
437
+ <path d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
438
+ </svg>
439
+ SVG
440
+ when :circle_check
441
+ <<~SVG.freeze
442
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
443
+ <circle cx="12" cy="12" r="10"/>
444
+ <path d="m9 12 2 2 4-4"/>
445
+ </svg>
446
+ SVG
447
+ when :circle_x
448
+ <<~SVG.freeze
449
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
450
+ <circle cx="12" cy="12" r="10"/>
451
+ <path d="m15 9-6 6"/>
452
+ <path d="m9 9 6 6"/>
453
+ </svg>
454
+ SVG
455
+ when :x
456
+ <<~SVG.freeze
457
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
458
+ <path d="M18 6 6 18"/>
459
+ <path d="m6 6 12 12"/>
460
+ </svg>
461
+ SVG
404
462
  end
405
463
  end
406
464
  end