maquina-components 0.5.1 → 0.6.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -2
  3. data/app/assets/stylesheets/alert.css +169 -135
  4. data/app/assets/stylesheets/badge.css +143 -138
  5. data/app/assets/stylesheets/breadcrumbs.css +183 -166
  6. data/app/assets/stylesheets/calendar.css +219 -206
  7. data/app/assets/stylesheets/card.css +126 -120
  8. data/app/assets/stylesheets/combobox.css +208 -191
  9. data/app/assets/stylesheets/date_picker.css +144 -134
  10. data/app/assets/stylesheets/drawer.css +158 -125
  11. data/app/assets/stylesheets/dropdown_menu.css +203 -190
  12. data/app/assets/stylesheets/empty.css +125 -117
  13. data/app/assets/stylesheets/form.css +625 -525
  14. data/app/assets/stylesheets/header.css +63 -47
  15. data/app/assets/stylesheets/menu_button.css +121 -98
  16. data/app/assets/stylesheets/pagination.css +130 -125
  17. data/app/assets/stylesheets/separator.css +23 -11
  18. data/app/assets/stylesheets/sidebar.css +447 -401
  19. data/app/assets/stylesheets/stats.css +77 -67
  20. data/app/assets/stylesheets/table.css +184 -166
  21. data/app/assets/stylesheets/toast.css +287 -278
  22. data/app/assets/stylesheets/toggle_group.css +132 -139
  23. data/app/assets/tailwind/maquina_components_engine/engine.css +54 -15
  24. data/app/assets/tailwind/maquina_components_engine/tokens.css +101 -0
  25. data/app/helpers/maquina_components/combobox_helper.rb +32 -24
  26. data/app/helpers/maquina_components/components_helper.rb +30 -6
  27. data/app/helpers/maquina_components/dropdown_menu_helper.rb +12 -12
  28. data/app/helpers/maquina_components/icons_helper.rb +26 -2
  29. data/app/javascript/controllers/drawer_trigger_controller.js +24 -3
  30. data/app/views/components/_label.html.erb +14 -0
  31. data/app/views/components/_table.html.erb +2 -2
  32. data/app/views/components/calendar/_week.html.erb +4 -12
  33. data/app/views/components/drawer/_close.html.erb +1 -1
  34. data/app/views/components/drawer/_description.html.erb +6 -0
  35. data/app/views/components/drawer/_header.html.erb +1 -1
  36. data/app/views/components/drawer/_provider.html.erb +13 -4
  37. data/app/views/components/drawer/_section.html.erb +7 -0
  38. data/app/views/components/drawer/_separator.html.erb +11 -0
  39. data/app/views/components/drawer/_title.html.erb +6 -0
  40. data/app/views/components/drawer/_trigger.html.erb +24 -7
  41. data/app/views/components/sidebar/_group_action.html.erb +26 -0
  42. data/app/views/components/sidebar/_menu_action.html.erb +28 -0
  43. data/app/views/components/sidebar/_menu_badge.html.erb +10 -0
  44. data/app/views/components/sidebar/_menu_button.html.erb +3 -2
  45. data/app/views/components/sidebar/_menu_link.html.erb +3 -2
  46. data/app/views/components/sidebar/_separator.html.erb +12 -0
  47. data/lib/generators/maquina_components/install/install_generator.rb +36 -1
  48. data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +5 -0
  49. data/lib/generators/maquina_components/install/templates/shape_state_tokens.css.tt +76 -0
  50. data/lib/generators/maquina_components/install/templates/theme.css.tt +10 -3
  51. data/lib/maquina-components.rb +29 -1
  52. data/lib/maquina_components/doctor.rb +280 -0
  53. data/lib/maquina_components/version.rb +1 -1
  54. data/lib/tasks/maquina_components_tasks.rake +16 -4
  55. metadata +14 -2
@@ -13,236 +13,249 @@
13
13
  * - item (clickable action)
14
14
  * - separator (divider)
15
15
  * - shortcut (keyboard hint)
16
+ *
17
+ * Cascade order in this file (full contract documented in form.css):
18
+ * 1. base [data-component="dropdown-menu"], [data-dropdown-menu-part="…"]
19
+ * 2. sizes [data-dropdown-menu-part="content"]:where([data-width="…"])
20
+ * 3. variants [data-dropdown-menu-part="item"]:where([data-variant="…"]),
21
+ * content :where([data-align="…"], [data-side="…"])
22
+ * 4. states […]:where(:hover, :focus, [disabled], [data-state="…"])
23
+ * 5. dark — none; dark-mode differences are tokens (see tokens.css)
24
+ * Every rule is flattened to 0,1,0 with :where() and lives in
25
+ * @layer components, so a caller's utility class wins and source order is
26
+ * the only tie-breaker. (Rules whose target is a bare element read 0,1,1 — an
27
+ * element selector cannot be zeroed away.) @keyframes sit outside the layer:
28
+ * they take no part in the cascade.
29
+ *
30
+ * The trigger carries data-component="button", so this file must be imported
31
+ * after form.css. See engine.css.
16
32
  */
17
33
 
18
- /* ===== Root Container ===== */
19
- [data-component="dropdown-menu"] {
20
- position: relative;
21
- display: inline-block;
22
- }
23
-
24
- /* ===== Trigger Button ===== */
25
- /* Trigger uses button component styles via data-component="button" */
26
- /* Additional dropdown-specific styles */
27
- [data-component="dropdown-menu"] [data-dropdown-menu-target="trigger"] {
28
- @apply gap-2;
29
- }
30
-
31
- /* Chevron icon rotation when open */
32
- [data-component="dropdown-menu"] [data-dropdown-menu-target="chevron"] {
33
- @apply size-4 shrink-0 opacity-50 transition-transform duration-200;
34
- }
35
-
36
- [data-component="dropdown-menu"][data-state="open"] [data-dropdown-menu-target="chevron"] {
37
- transform: rotate(180deg);
38
- }
39
-
40
- /* ===== Content Container ===== */
41
- [data-dropdown-menu-part="content"] {
42
- position: absolute;
43
- z-index: 50;
44
- display: none;
45
- flex-direction: column;
46
- overflow: hidden;
47
- background-color: var(--popover, var(--background));
48
- color: var(--popover-foreground, var(--foreground));
49
- @apply min-w-[8rem] rounded-md border p-1 shadow-md;
50
- border-color: var(--border);
51
-
52
- /* Animation setup */
53
- @apply transition-all duration-150 ease-out;
54
- }
34
+ @layer components {
35
+ /* ===== Root Container ===== */
36
+ [data-component="dropdown-menu"] {
37
+ position: relative;
38
+ display: inline-block;
39
+ }
55
40
 
56
- /* Visible state */
57
- [data-dropdown-menu-part="content"][data-state="open"] {
58
- display: flex;
59
- }
41
+ /* ===== Trigger Button ===== */
42
+ /* Trigger uses button component styles via data-component="button" */
43
+ /* Additional dropdown-specific styles */
44
+ :where([data-component="dropdown-menu"]) [data-dropdown-menu-target="trigger"] {
45
+ @apply gap-2;
46
+ }
60
47
 
61
- /* Animation states */
62
- [data-dropdown-menu-part="content"][data-state="open"] {
63
- animation: dropdown-menu-in 150ms ease-out;
64
- }
48
+ /* Chevron icon rotation when open */
49
+ :where([data-component="dropdown-menu"]) [data-dropdown-menu-target="chevron"] {
50
+ @apply size-4 shrink-0 opacity-50 transition-transform duration-200;
51
+ }
65
52
 
66
- [data-dropdown-menu-part="content"][data-state="closing"] {
67
- display: flex;
68
- animation: dropdown-menu-out 100ms ease-in forwards;
69
- }
53
+ :where([data-component="dropdown-menu"][data-state="open"]) [data-dropdown-menu-target="chevron"] {
54
+ transform: rotate(180deg);
55
+ }
70
56
 
71
- @keyframes dropdown-menu-in {
72
- from {
73
- opacity: 0;
74
- transform: scale(0.95) translateY(-4px);
57
+ /* ===== Content Container ===== */
58
+ [data-dropdown-menu-part="content"] {
59
+ position: absolute;
60
+ z-index: 50;
61
+ display: none;
62
+ flex-direction: column;
63
+ overflow: hidden;
64
+ background-color: var(--popover, var(--background));
65
+ color: var(--popover-foreground, var(--foreground));
66
+ @apply min-w-[8rem] border p-1;
67
+ border-radius: var(--dropdown-menu-radius, var(--surface-radius, 0.5rem));
68
+ box-shadow: var(--dropdown-menu-shadow, var(--elevation-overlay, var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1))));
69
+ border-color: var(--border);
70
+
71
+ /* Animation setup */
72
+ @apply transition-all duration-150 ease-out;
75
73
  }
76
- to {
77
- opacity: 1;
78
- transform: scale(1) translateY(0);
74
+
75
+ /* ===== Width Variants ===== */
76
+ [data-dropdown-menu-part="content"]:where([data-width="default"]) {
77
+ @apply min-w-[8rem];
79
78
  }
80
- }
81
79
 
82
- @keyframes dropdown-menu-out {
83
- from {
84
- opacity: 1;
85
- transform: scale(1) translateY(0);
80
+ [data-dropdown-menu-part="content"]:where([data-width="sm"]) {
81
+ @apply w-40;
86
82
  }
87
- to {
88
- opacity: 0;
89
- transform: scale(0.95) translateY(-4px);
83
+
84
+ [data-dropdown-menu-part="content"]:where([data-width="md"]) {
85
+ @apply w-56;
90
86
  }
91
- }
92
87
 
93
- /* ===== Width Variants ===== */
94
- [data-dropdown-menu-part="content"][data-width="default"] {
95
- @apply min-w-[8rem];
96
- }
88
+ [data-dropdown-menu-part="content"]:where([data-width="lg"]) {
89
+ @apply w-72;
90
+ }
97
91
 
98
- [data-dropdown-menu-part="content"][data-width="sm"] {
99
- @apply w-40;
100
- }
92
+ /* ===== Alignment ===== */
93
+ [data-dropdown-menu-part="content"]:where([data-align="start"]) {
94
+ left: 0;
95
+ }
101
96
 
102
- [data-dropdown-menu-part="content"][data-width="md"] {
103
- @apply w-56;
104
- }
97
+ [data-dropdown-menu-part="content"]:where([data-align="center"]) {
98
+ left: 50%;
99
+ transform: translateX(-50%);
100
+ }
105
101
 
106
- [data-dropdown-menu-part="content"][data-width="lg"] {
107
- @apply w-72;
108
- }
102
+ [data-dropdown-menu-part="content"]:where([data-align="end"]) {
103
+ right: 0;
104
+ }
109
105
 
110
- /* ===== Alignment ===== */
111
- [data-dropdown-menu-part="content"][data-align="start"] {
112
- left: 0;
113
- }
106
+ /* ===== Side Positioning ===== */
107
+ [data-dropdown-menu-part="content"]:where([data-side="bottom"]) {
108
+ top: 100%;
109
+ @apply mt-1;
110
+ }
114
111
 
115
- [data-dropdown-menu-part="content"][data-align="center"] {
116
- left: 50%;
117
- transform: translateX(-50%);
118
- }
112
+ [data-dropdown-menu-part="content"]:where([data-side="top"]) {
113
+ bottom: 100%;
114
+ @apply mb-1;
115
+ }
119
116
 
120
- [data-dropdown-menu-part="content"][data-align="center"][data-state="open"] {
121
- animation: dropdown-menu-in-center 150ms ease-out;
122
- }
117
+ [data-dropdown-menu-part="content"]:where([data-side="left"]) {
118
+ right: 100%;
119
+ top: 0;
120
+ @apply mr-1;
121
+ }
123
122
 
124
- @keyframes dropdown-menu-in-center {
125
- from {
126
- opacity: 0;
127
- transform: translateX(-50%) scale(0.95) translateY(-4px);
123
+ [data-dropdown-menu-part="content"]:where([data-side="right"]) {
124
+ left: 100%;
125
+ top: 0;
126
+ @apply ml-1;
128
127
  }
129
- to {
130
- opacity: 1;
131
- transform: translateX(-50%) scale(1) translateY(0);
128
+
129
+ /* ===== Content States (open / closing) ===== */
130
+ [data-dropdown-menu-part="content"]:where([data-state="open"]) {
131
+ display: flex;
132
+ animation: dropdown-menu-in 150ms ease-out;
132
133
  }
133
- }
134
134
 
135
- [data-dropdown-menu-part="content"][data-align="end"] {
136
- right: 0;
137
- }
135
+ [data-dropdown-menu-part="content"]:where([data-align="center"][data-state="open"]) {
136
+ animation: dropdown-menu-in-center 150ms ease-out;
137
+ }
138
138
 
139
- /* ===== Side Positioning ===== */
140
- [data-dropdown-menu-part="content"][data-side="bottom"] {
141
- top: 100%;
142
- @apply mt-1;
143
- }
139
+ [data-dropdown-menu-part="content"]:where([data-state="closing"]) {
140
+ display: flex;
141
+ animation: dropdown-menu-out 100ms ease-in forwards;
142
+ }
144
143
 
145
- [data-dropdown-menu-part="content"][data-side="top"] {
146
- bottom: 100%;
147
- @apply mb-1;
148
- }
144
+ /* ===== Menu Item ===== */
145
+ [data-dropdown-menu-part="item"] {
146
+ position: relative;
147
+ display: flex;
148
+ align-items: center;
149
+ cursor: default;
150
+ user-select: none;
151
+ color: var(--popover-foreground, var(--foreground));
152
+ @apply gap-2 px-2 py-1.5 text-sm outline-none transition-colors;
153
+ border-radius: var(--dropdown-menu-item-radius, var(--control-radius, 0.375rem));
154
+ }
149
155
 
150
- [data-dropdown-menu-part="content"][data-side="left"] {
151
- right: 100%;
152
- top: 0;
153
- @apply mr-1;
154
- }
156
+ /* Reset link styles */
157
+ [data-dropdown-menu-part="item"]:where(a) {
158
+ text-decoration: none;
159
+ color: inherit;
160
+ }
155
161
 
156
- [data-dropdown-menu-part="content"][data-side="right"] {
157
- left: 100%;
158
- top: 0;
159
- @apply ml-1;
160
- }
162
+ /* Icon support */
163
+ [data-dropdown-menu-part="item"] svg {
164
+ @apply size-4 shrink-0 pointer-events-none;
165
+ }
161
166
 
162
- /* ===== Menu Item ===== */
163
- [data-dropdown-menu-part="item"] {
164
- position: relative;
165
- display: flex;
166
- align-items: center;
167
- cursor: default;
168
- user-select: none;
169
- color: var(--popover-foreground, var(--foreground));
170
- @apply gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors;
171
- }
167
+ /* Destructive variant */
168
+ [data-dropdown-menu-part="item"]:where([data-variant="destructive"]) {
169
+ color: var(--destructive);
170
+ }
172
171
 
173
- /* Reset link styles */
174
- a[data-dropdown-menu-part="item"] {
175
- text-decoration: none;
176
- color: inherit;
177
- }
172
+ /* ----- Item states ----- */
173
+ [data-dropdown-menu-part="item"]:where(:hover:not([disabled]):not([aria-disabled="true"])) {
174
+ background-color: var(--accent-hover, var(--accent));
175
+ color: var(--accent-foreground);
176
+ cursor: pointer;
177
+ }
178
178
 
179
- /* Hover state */
180
- [data-dropdown-menu-part="item"]:hover:not([disabled]):not([aria-disabled="true"]) {
181
- background-color: var(--accent);
182
- color: var(--accent-foreground);
183
- cursor: pointer;
184
- }
179
+ /* Focus state (keyboard navigation) */
180
+ [data-dropdown-menu-part="item"]:where(:focus) {
181
+ background-color: var(--accent);
182
+ color: var(--accent-foreground);
183
+ }
185
184
 
186
- /* Focus state (keyboard navigation) */
187
- [data-dropdown-menu-part="item"]:focus {
188
- background-color: var(--accent);
189
- color: var(--accent-foreground);
190
- }
185
+ [data-dropdown-menu-part="item"]:where(
186
+ [data-variant="destructive"]:hover:not([disabled]):not([aria-disabled="true"]),
187
+ [data-variant="destructive"]:focus
188
+ ) {
189
+ background-color: var(--destructive-hover, var(--destructive));
190
+ color: var(--destructive-foreground);
191
+ }
191
192
 
192
- /* Disabled state */
193
- [data-dropdown-menu-part="item"][disabled],
194
- [data-dropdown-menu-part="item"][aria-disabled="true"] {
195
- @apply opacity-50 cursor-not-allowed pointer-events-none;
196
- }
193
+ /* Disabled state */
194
+ [data-dropdown-menu-part="item"]:where([disabled], [aria-disabled="true"]) {
195
+ @apply opacity-50 cursor-not-allowed pointer-events-none;
196
+ }
197
197
 
198
- /* Destructive variant */
199
- [data-dropdown-menu-part="item"][data-variant="destructive"] {
200
- color: var(--destructive);
201
- }
198
+ /* ===== Label ===== */
199
+ [data-dropdown-menu-part="label"] {
200
+ @apply px-2 py-1.5 text-sm font-semibold;
201
+ color: var(--foreground);
202
+ }
202
203
 
203
- [data-dropdown-menu-part="item"][data-variant="destructive"]:hover:not([disabled]):not([aria-disabled="true"]),
204
- [data-dropdown-menu-part="item"][data-variant="destructive"]:focus {
205
- background-color: var(--destructive);
206
- color: var(--destructive-foreground);
207
- }
204
+ /* Inset variant (aligns with items that have icons) */
205
+ [data-dropdown-menu-part="label"]:where([data-inset="true"]) {
206
+ @apply pl-8;
207
+ }
208
208
 
209
- /* Icon support */
210
- [data-dropdown-menu-part="item"] svg {
211
- @apply size-4 shrink-0 pointer-events-none;
212
- }
209
+ /* ===== Separator ===== */
210
+ [data-dropdown-menu-part="separator"] {
211
+ @apply -mx-1 my-1 h-px;
212
+ background-color: var(--muted);
213
+ }
213
214
 
214
- /* ===== Label ===== */
215
- [data-dropdown-menu-part="label"] {
216
- @apply px-2 py-1.5 text-sm font-semibold;
217
- color: var(--foreground);
218
- }
215
+ /* ===== Group ===== */
216
+ [data-dropdown-menu-part="group"] {
217
+ display: flex;
218
+ flex-direction: column;
219
+ }
219
220
 
220
- /* Inset variant (aligns with items that have icons) */
221
- [data-dropdown-menu-part="label"][data-inset="true"] {
222
- @apply pl-8;
221
+ /* ===== Shortcut ===== */
222
+ [data-dropdown-menu-part="shortcut"] {
223
+ @apply ml-auto text-xs tracking-widest;
224
+ color: var(--muted-foreground);
225
+ }
223
226
  }
224
227
 
225
- /* ===== Separator ===== */
226
- [data-dropdown-menu-part="separator"] {
227
- @apply -mx-1 my-1 h-px;
228
- background-color: var(--muted);
228
+ /* ===== Keyframes ===== */
229
+ /* Outside the layer: @keyframes are not cascade participants. */
230
+ @keyframes dropdown-menu-in {
231
+ from {
232
+ opacity: 0;
233
+ transform: scale(0.95) translateY(-4px);
234
+ }
235
+ to {
236
+ opacity: 1;
237
+ transform: scale(1) translateY(0);
238
+ }
229
239
  }
230
240
 
231
- /* ===== Group ===== */
232
- [data-dropdown-menu-part="group"] {
233
- display: flex;
234
- flex-direction: column;
241
+ @keyframes dropdown-menu-out {
242
+ from {
243
+ opacity: 1;
244
+ transform: scale(1) translateY(0);
245
+ }
246
+ to {
247
+ opacity: 0;
248
+ transform: scale(0.95) translateY(-4px);
249
+ }
235
250
  }
236
251
 
237
- /* ===== Shortcut ===== */
238
- [data-dropdown-menu-part="shortcut"] {
239
- @apply ml-auto text-xs tracking-widest;
240
- color: var(--muted-foreground);
252
+ @keyframes dropdown-menu-in-center {
253
+ from {
254
+ opacity: 0;
255
+ transform: translateX(-50%) scale(0.95) translateY(-4px);
256
+ }
257
+ to {
258
+ opacity: 1;
259
+ transform: translateX(-50%) scale(1) translateY(0);
260
+ }
241
261
  }
242
-
243
- /* ===== Dark Mode ===== */
244
- /*
245
- * Dark mode is handled automatically through CSS variables.
246
- * The theme variables change based on the .dark class on html/body.
247
- * No additional dark mode styles needed here.
248
- */