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
@@ -16,238 +16,255 @@
16
16
  * - label (section heading)
17
17
  * - separator (divider)
18
18
  * - empty (no results message)
19
+ *
20
+ * Cascade order in this file (full contract documented in form.css):
21
+ * 1. base [data-component="combobox"], [data-combobox-part="…"]
22
+ * 2. sizes [data-combobox-part="content"]:where([data-width="…"])
23
+ * 3. variants :where([data-align="…"], [data-has-value="false"])
24
+ * 4. states […]:where(:hover, :focus, [data-selected], [aria-disabled], [hidden])
25
+ * 5. dark — none; dark-mode differences are tokens (see tokens.css)
26
+ * Every rule is flattened to 0,1,0 with :where() and lives in
27
+ * @layer components, so a caller's utility class wins and source order is
28
+ * the only tie-breaker. (Rules whose target is a bare element read 0,1,1 — an
29
+ * element selector cannot be zeroed away.) @keyframes sit outside the layer:
30
+ * they take no part in the cascade.
31
+ *
32
+ * The trigger carries data-component="button" (it is where the trigger's
33
+ * justify-content and min-width have to beat the button base), so this file
34
+ * must be imported after form.css. See engine.css.
19
35
  */
20
36
 
21
- /* ===== Root Container ===== */
22
- [data-component="combobox"] {
23
- position: relative;
24
- display: inline-block;
25
- }
26
-
27
- /* ===== Trigger Button ===== */
28
- /* Trigger uses button component styles via data-component="button" */
29
- [data-combobox-part="trigger"] {
30
- @apply justify-between gap-2;
31
- min-width: 200px;
32
- }
33
-
34
- [data-combobox-part="trigger"] [data-combobox-target="label"] {
35
- @apply truncate text-left flex-1;
36
- }
37
-
38
- /* Placeholder state */
39
- [data-combobox-part="trigger"][data-has-value="false"] [data-combobox-target="label"] {
40
- color: var(--muted-foreground);
41
- }
42
-
43
- /* Icon styling */
44
- [data-combobox-part="trigger"] > svg {
45
- @apply size-4 shrink-0 opacity-50;
46
- }
47
-
48
- /* ===== Content (Popover) ===== */
49
- [data-combobox-part="content"] {
50
- background-color: var(--popover, var(--background));
51
- color: var(--popover-foreground, var(--foreground));
52
- border-color: var(--border);
53
- @apply rounded-md border p-0 shadow-md outline-none;
54
- @apply min-w-[200px];
55
-
56
- /* Reset popover defaults - positioning below (anchor CSS) or in JS fallback */
57
- margin: 0;
58
- padding: 0;
59
- inset: unset;
60
- }
37
+ @layer components {
38
+ /* ===== Root Container ===== */
39
+ [data-component="combobox"] {
40
+ position: relative;
41
+ display: inline-block;
42
+ }
61
43
 
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) {
44
+ /* ===== Trigger Button ===== */
45
+ /* Trigger uses button component styles via data-component="button" */
68
46
  [data-combobox-part="trigger"] {
69
- anchor-name: --maquina-combobox;
47
+ @apply justify-between gap-2;
48
+ min-width: 200px;
70
49
  }
71
50
 
72
- [data-combobox-part="content"] {
73
- position: fixed;
74
- position-anchor: --maquina-combobox;
75
- margin-block-start: 4px;
76
- position-try-fallbacks: flip-block;
51
+ :where([data-combobox-part="trigger"]) [data-combobox-target="label"] {
52
+ @apply truncate text-left flex-1;
77
53
  }
78
54
 
79
- [data-combobox-part="content"][data-align="start"] {
80
- position-area: block-end span-inline-end;
55
+ /* Icon styling */
56
+ :where([data-combobox-part="trigger"]) > svg {
57
+ @apply size-4 shrink-0 opacity-50;
81
58
  }
82
59
 
83
- [data-combobox-part="content"][data-align="center"] {
84
- position-area: block-end;
60
+ /* Placeholder state */
61
+ :where([data-combobox-part="trigger"][data-has-value="false"]) [data-combobox-target="label"] {
62
+ color: var(--muted-foreground);
85
63
  }
86
64
 
87
- [data-combobox-part="content"][data-align="end"] {
88
- position-area: block-end span-inline-start;
65
+ /* ===== Content (Popover) ===== */
66
+ [data-combobox-part="content"] {
67
+ background-color: var(--popover, var(--background));
68
+ color: var(--popover-foreground, var(--foreground));
69
+ border-color: var(--border);
70
+ @apply border p-0 outline-none;
71
+ border-radius: var(--combobox-radius, var(--surface-radius, 0.5rem));
72
+ box-shadow: var(--combobox-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))));
73
+ @apply min-w-[200px];
74
+
75
+ /* Reset popover defaults - positioning below (anchor CSS) or in JS fallback */
76
+ margin: 0;
77
+ padding: 0;
78
+ inset: unset;
89
79
  }
90
80
 
91
- [data-combobox-part="content"][data-width="default"] {
92
- width: auto;
93
- min-width: max(200px, anchor-size(inline));
81
+ /* ===== Anchor Positioning (modern browsers) ===== */
82
+ /* Places the top-layer popover declaratively; combobox_controller skips its
83
+ measured fallback when these features are supported. flip-block flips the
84
+ popover above the trigger when there is no room below — something the JS
85
+ fallback never did. */
86
+ @supports (anchor-name: --a) and (position-area: block-end) {
87
+ [data-combobox-part="trigger"] {
88
+ anchor-name: --maquina-combobox;
89
+ }
90
+
91
+ [data-combobox-part="content"] {
92
+ position: fixed;
93
+ position-anchor: --maquina-combobox;
94
+ margin-block-start: 4px;
95
+ position-try-fallbacks: flip-block;
96
+ }
97
+
98
+ [data-combobox-part="content"]:where([data-align="start"]) {
99
+ position-area: block-end span-inline-end;
100
+ }
101
+
102
+ [data-combobox-part="content"]:where([data-align="center"]) {
103
+ position-area: block-end;
104
+ }
105
+
106
+ [data-combobox-part="content"]:where([data-align="end"]) {
107
+ position-area: block-end span-inline-start;
108
+ }
109
+
110
+ [data-combobox-part="content"]:where([data-width="default"]) {
111
+ width: auto;
112
+ min-width: max(200px, anchor-size(inline));
113
+ }
94
114
  }
95
- }
96
-
97
- /* Width variants */
98
- [data-combobox-part="content"][data-width="sm"] {
99
- @apply w-40;
100
- }
101
115
 
102
- [data-combobox-part="content"][data-width="default"] {
103
- @apply w-[200px];
104
- }
116
+ /* Width variants */
117
+ [data-combobox-part="content"]:where([data-width="sm"]) {
118
+ @apply w-40;
119
+ }
105
120
 
106
- [data-combobox-part="content"][data-width="md"] {
107
- @apply w-56;
108
- }
121
+ [data-combobox-part="content"]:where([data-width="default"]) {
122
+ @apply w-[200px];
123
+ }
109
124
 
110
- [data-combobox-part="content"][data-width="lg"] {
111
- @apply w-72;
112
- }
125
+ [data-combobox-part="content"]:where([data-width="md"]) {
126
+ @apply w-56;
127
+ }
113
128
 
114
- [data-combobox-part="content"][data-width="full"] {
115
- @apply w-full;
116
- }
129
+ [data-combobox-part="content"]:where([data-width="lg"]) {
130
+ @apply w-72;
131
+ }
117
132
 
118
- /* Alignment is handled by JavaScript for proper popover positioning */
133
+ [data-combobox-part="content"]:where([data-width="full"]) {
134
+ @apply w-full;
135
+ }
119
136
 
120
- /* Animation */
121
- [data-combobox-part="content"]:popover-open {
122
- animation: combobox-in 150ms ease-out;
123
- }
137
+ /* Animation */
138
+ [data-combobox-part="content"]:where(:popover-open) {
139
+ animation: combobox-in 150ms ease-out;
140
+ }
124
141
 
125
- @keyframes combobox-in {
126
- from {
127
- opacity: 0;
128
- transform: scale(0.95) translateY(-4px);
142
+ /* ===== Search Input ===== */
143
+ [data-combobox-part="input-wrapper"] {
144
+ display: flex;
145
+ align-items: center;
146
+ border-bottom: 1px solid var(--border);
147
+ @apply px-3 gap-2;
129
148
  }
130
- to {
131
- opacity: 1;
132
- transform: scale(1) translateY(0);
149
+
150
+ :where([data-combobox-part="input-wrapper"]) > svg {
151
+ @apply size-4 shrink-0 opacity-50;
133
152
  }
134
- }
135
153
 
136
- /* ===== Search Input ===== */
137
- [data-combobox-part="input-wrapper"] {
138
- display: flex;
139
- align-items: center;
140
- border-bottom: 1px solid var(--border);
141
- @apply px-3 gap-2;
142
- }
154
+ [data-combobox-part="input"] {
155
+ background-color: transparent;
156
+ color: var(--foreground);
157
+ @apply flex h-10 w-full py-3 text-sm outline-none;
158
+ @apply placeholder:text-muted-foreground;
159
+ @apply disabled:cursor-not-allowed disabled:opacity-50;
160
+ }
143
161
 
144
- [data-combobox-part="input-wrapper"] > svg {
145
- @apply size-4 shrink-0 opacity-50;
146
- }
162
+ /* ===== Options List ===== */
163
+ [data-combobox-part="list"] {
164
+ @apply max-h-[300px] overflow-y-auto overflow-x-hidden p-1;
165
+ }
147
166
 
148
- [data-combobox-part="input"] {
149
- background-color: transparent;
150
- color: var(--foreground);
151
- @apply flex h-10 w-full py-3 text-sm outline-none;
152
- @apply placeholder:text-muted-foreground;
153
- @apply disabled:cursor-not-allowed disabled:opacity-50;
154
- }
167
+ /* ===== Option Item ===== */
168
+ [data-combobox-part="option"] {
169
+ position: relative;
170
+ display: flex;
171
+ align-items: center;
172
+ cursor: default;
173
+ user-select: none;
174
+ color: var(--popover-foreground, var(--foreground));
175
+ @apply gap-2 py-1.5 text-sm outline-none transition-colors;
176
+ border-radius: var(--combobox-item-radius, var(--control-radius, 0.375rem));
177
+ @apply pl-8 pr-2; /* Space for check icon on left */
178
+ }
155
179
 
156
- /* ===== Options List ===== */
157
- [data-combobox-part="list"] {
158
- @apply max-h-[300px] overflow-y-auto overflow-x-hidden p-1;
159
- }
180
+ /* ----- Option states ----- */
160
181
 
161
- /* ===== Option Item ===== */
162
- [data-combobox-part="option"] {
163
- position: relative;
164
- display: flex;
165
- align-items: center;
166
- cursor: default;
167
- user-select: none;
168
- color: var(--popover-foreground, var(--foreground));
169
- @apply gap-2 rounded-sm py-1.5 text-sm outline-none transition-colors;
170
- @apply pl-8 pr-2; /* Space for check icon on left */
171
- }
182
+ /* Hover state */
183
+ [data-combobox-part="option"]:where(:hover:not([aria-disabled="true"])) {
184
+ background-color: var(--accent-hover, var(--accent));
185
+ color: var(--accent-foreground);
186
+ cursor: pointer;
187
+ }
172
188
 
173
- /* Hover state */
174
- [data-combobox-part="option"]:hover:not([aria-disabled="true"]) {
175
- background-color: var(--accent);
176
- color: var(--accent-foreground);
177
- cursor: pointer;
178
- }
189
+ /* Focus state (keyboard navigation) */
190
+ [data-combobox-part="option"]:where(:focus) {
191
+ background-color: var(--accent);
192
+ color: var(--accent-foreground);
193
+ }
179
194
 
180
- /* Focus state (keyboard navigation) */
181
- [data-combobox-part="option"]:focus {
182
- background-color: var(--accent);
183
- color: var(--accent-foreground);
184
- }
195
+ /* Selected state */
196
+ [data-combobox-part="option"]:where([data-selected="true"]) {
197
+ background-color: var(--accent);
198
+ color: var(--accent-foreground);
199
+ }
185
200
 
186
- /* Selected state */
187
- [data-combobox-part="option"][data-selected="true"] {
188
- background-color: var(--accent);
189
- color: var(--accent-foreground);
190
- }
201
+ /* Disabled state */
202
+ [data-combobox-part="option"]:where([aria-disabled="true"]) {
203
+ @apply opacity-50 cursor-not-allowed pointer-events-none;
204
+ }
191
205
 
192
- /* Disabled state */
193
- [data-combobox-part="option"][aria-disabled="true"] {
194
- @apply opacity-50 cursor-not-allowed pointer-events-none;
195
- }
206
+ /* Hidden state (filtered out) */
207
+ [data-combobox-part="option"]:where([hidden]) {
208
+ display: none;
209
+ }
196
210
 
197
- /* Hidden state (filtered out) */
198
- [data-combobox-part="option"][hidden] {
199
- display: none;
200
- }
211
+ /* Check indicator */
212
+ [data-combobox-part="check"] {
213
+ position: absolute;
214
+ left: 8px;
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: center;
218
+ @apply size-4;
219
+ }
201
220
 
202
- /* Check indicator */
203
- [data-combobox-part="check"] {
204
- position: absolute;
205
- left: 8px;
206
- display: flex;
207
- align-items: center;
208
- justify-content: center;
209
- @apply size-4;
210
- }
221
+ [data-combobox-part="check"] svg {
222
+ @apply size-4;
223
+ }
211
224
 
212
- [data-combobox-part="check"].invisible {
213
- visibility: hidden;
214
- }
225
+ [data-combobox-part="check"]:where(.invisible) {
226
+ visibility: hidden;
227
+ }
215
228
 
216
- [data-combobox-part="check"] svg {
217
- @apply size-4;
218
- }
229
+ /* ===== Empty State ===== */
230
+ [data-combobox-part="empty"] {
231
+ @apply py-6 text-center text-sm;
232
+ color: var(--muted-foreground);
233
+ }
219
234
 
220
- /* ===== Empty State ===== */
221
- [data-combobox-part="empty"] {
222
- @apply py-6 text-center text-sm;
223
- color: var(--muted-foreground);
224
- }
235
+ [data-combobox-part="empty"]:where([hidden]) {
236
+ display: none;
237
+ }
225
238
 
226
- [data-combobox-part="empty"][hidden] {
227
- display: none;
228
- }
239
+ /* ===== Group ===== */
240
+ [data-combobox-part="group"] {
241
+ display: flex;
242
+ flex-direction: column;
243
+ }
229
244
 
230
- /* ===== Group ===== */
231
- [data-combobox-part="group"] {
232
- display: flex;
233
- flex-direction: column;
234
- }
245
+ /* ===== Label ===== */
246
+ [data-combobox-part="label"] {
247
+ @apply px-2 py-1.5 text-xs;
248
+ font-weight: var(--label-weight, 500);
249
+ color: var(--muted-foreground);
250
+ }
235
251
 
236
- /* ===== Label ===== */
237
- [data-combobox-part="label"] {
238
- @apply px-2 py-1.5 text-xs font-medium;
239
- color: var(--muted-foreground);
252
+ /* ===== Separator ===== */
253
+ [data-combobox-part="separator"] {
254
+ @apply -mx-1 my-1 h-px;
255
+ background-color: var(--border);
256
+ }
240
257
  }
241
258
 
242
- /* ===== Separator ===== */
243
- [data-combobox-part="separator"] {
244
- @apply -mx-1 my-1 h-px;
245
- background-color: var(--border);
259
+ /* ===== Keyframes ===== */
260
+ /* Outside the layer: @keyframes are not cascade participants. */
261
+ @keyframes combobox-in {
262
+ from {
263
+ opacity: 0;
264
+ transform: scale(0.95) translateY(-4px);
265
+ }
266
+ to {
267
+ opacity: 1;
268
+ transform: scale(1) translateY(0);
269
+ }
246
270
  }
247
-
248
- /* ===== Dark Mode ===== */
249
- /*
250
- * Dark mode is handled automatically through CSS variables.
251
- * The theme variables change based on the .dark class on html/body.
252
- * No additional dark mode styles needed here.
253
- */