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
@@ -8,165 +8,175 @@
8
8
  * - date-picker (root container)
9
9
  * - trigger (button with popovertarget)
10
10
  * - popover (native popover with calendar)
11
+ *
12
+ * Cascade order in this file (full contract documented in form.css):
13
+ * 1. base [data-component="date-picker"], [data-date-picker-part="…"]
14
+ * 2. sizes :where([data-size="sm"|"lg"]) [data-date-picker-part="trigger"]
15
+ * 3. variants :where([data-full-width]) …
16
+ * 4. states […]:where(:hover, :focus-visible, :disabled, [aria-expanded],
17
+ * :has(input:invalid))
18
+ * 5. dark — none; dark-mode differences are tokens (see tokens.css)
19
+ * Every rule is flattened to 0,1,0 with :where() and lives in
20
+ * @layer components, so a caller's utility class wins and source order is
21
+ * the only tie-breaker. (Rules whose target is a bare element read 0,1,1 — an
22
+ * element selector cannot be zeroed away.)
23
+ *
24
+ * This file must be imported after calendar.css: the "calendar inside popover"
25
+ * rule below strips the calendar's own border, and at a flat 0,1,0 only source
26
+ * order decides that. See engine.css.
11
27
  */
12
28
 
13
- /* ===== Root Container ===== */
14
- [data-component="date-picker"] {
15
- position: relative;
16
- display: inline-block;
17
- }
29
+ @layer components {
30
+ /* ===== Root Container ===== */
31
+ [data-component="date-picker"] {
32
+ position: relative;
33
+ display: inline-block;
34
+ }
18
35
 
19
- /* ===== Trigger Button ===== */
20
- [data-date-picker-part="trigger"] {
21
- display: inline-flex;
22
- align-items: center;
23
- justify-content: flex-start;
24
- gap: 0.5rem;
25
- width: 100%;
26
- min-width: 200px;
27
- @apply h-9 px-3 py-2 text-sm rounded-md;
28
- border: 1px solid var(--input);
29
- background-color: var(--background);
30
- color: var(--foreground);
31
- cursor: pointer;
32
- text-align: left;
33
- @apply transition-colors duration-150;
34
- }
36
+ /* ===== Trigger Button ===== */
37
+ [data-date-picker-part="trigger"] {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ justify-content: flex-start;
41
+ gap: 0.5rem;
42
+ width: 100%;
43
+ min-width: 200px;
44
+ @apply h-9 px-3 py-2 text-sm;
45
+ border-radius: var(--date-picker-radius, var(--control-radius, 0.375rem));
46
+ border: 1px solid var(--input);
47
+ background-color: var(--control-fill, var(--background));
48
+ color: var(--foreground);
49
+ cursor: pointer;
50
+ text-align: left;
51
+ @apply transition-colors duration-150;
52
+ }
35
53
 
36
- [data-date-picker-part="trigger"]:hover:not(:disabled) {
37
- background-color: var(--accent);
38
- }
54
+ [data-date-picker-part="trigger"] svg {
55
+ @apply size-4 shrink-0;
56
+ color: var(--muted-foreground);
57
+ }
39
58
 
40
- [data-date-picker-part="trigger"]:focus-visible {
41
- @apply outline-none;
42
- border-color: var(--ring);
43
- box-shadow: 0 0 0 2px var(--background),
44
- 0 0 0 4px var(--ring);
45
- }
59
+ /* ===== Trigger Sizes ===== */
60
+ :where([data-component="date-picker"][data-size="sm"]) [data-date-picker-part="trigger"] {
61
+ @apply h-8 px-2 py-1 text-xs;
62
+ min-width: 160px;
63
+ }
46
64
 
47
- [data-date-picker-part="trigger"]:disabled {
48
- @apply opacity-50 cursor-not-allowed;
49
- }
65
+ :where([data-component="date-picker"][data-size="sm"]) [data-date-picker-part="trigger"] svg {
66
+ @apply size-3.5;
67
+ }
50
68
 
51
- [data-date-picker-part="trigger"] svg {
52
- @apply size-4 shrink-0;
53
- color: var(--muted-foreground);
54
- }
69
+ :where([data-component="date-picker"][data-size="lg"]) [data-date-picker-part="trigger"] {
70
+ @apply h-11 px-4 py-3 text-base;
71
+ min-width: 240px;
72
+ }
55
73
 
56
- /* Placeholder state */
57
- [data-date-picker-part="trigger"]:has([data-date-picker-part="placeholder-indicator"]) {
58
- color: var(--muted-foreground);
59
- }
74
+ :where([data-component="date-picker"][data-size="lg"]) [data-date-picker-part="trigger"] svg {
75
+ @apply size-5;
76
+ }
60
77
 
61
- /* ===== Popover ===== */
62
- [data-date-picker-part="popover"] {
63
- @apply p-0 rounded-lg border;
64
- background-color: var(--background);
65
- border-color: var(--border);
66
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
67
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
68
-
69
- /* Reset default popover styles */
70
- margin: 0;
71
- overflow: visible;
72
-
73
- /* Position below trigger */
74
- position-area: bottom span-right;
75
- position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
76
- margin-top: 0.25rem;
77
-
78
- /* Entry animation */
79
- opacity: 1;
80
- transform: translateY(0) scale(1);
81
- transition: opacity 150ms ease-out,
82
- transform 150ms ease-out;
83
- }
78
+ /* ===== Full width variant ===== */
79
+ [data-component="date-picker"]:where([data-full-width]) {
80
+ display: block;
81
+ width: 100%;
82
+ }
84
83
 
85
- /* Closed state - for exit animation */
86
- [data-date-picker-part="popover"]:not(:popover-open) {
87
- opacity: 0;
88
- transform: translateY(-0.5rem) scale(0.95);
89
- }
84
+ :where([data-component="date-picker"][data-full-width]) [data-date-picker-part="trigger"] {
85
+ width: 100%;
86
+ }
90
87
 
91
- /* Entry animation starting state */
92
- @starting-style {
93
- [data-date-picker-part="popover"]:popover-open {
94
- opacity: 0;
95
- transform: translateY(-0.5rem) scale(0.95);
88
+ /* ===== Trigger States ===== */
89
+
90
+ /* Placeholder state */
91
+ [data-date-picker-part="trigger"]:where(:has([data-date-picker-part="placeholder-indicator"])) {
92
+ color: var(--muted-foreground);
96
93
  }
97
- }
98
94
 
99
- /* Backdrop */
100
- [data-date-picker-part="popover"]::backdrop {
101
- background-color: transparent;
102
- }
95
+ [data-date-picker-part="trigger"]:where(:hover:not(:disabled)) {
96
+ background-color: var(--accent-hover, var(--accent));
97
+ }
103
98
 
104
- /* ===== Calendar inside popover adjustments ===== */
105
- [data-date-picker-part="popover"] [data-component="calendar"] {
106
- border: none;
107
- box-shadow: none;
108
- }
99
+ [data-date-picker-part="trigger"]:where([aria-expanded="true"]) {
100
+ border-color: var(--ring);
101
+ }
109
102
 
110
- /* ===== Trigger aria-expanded state ===== */
111
- [data-date-picker-part="trigger"][aria-expanded="true"] {
112
- border-color: var(--ring);
113
- }
103
+ [data-date-picker-part="trigger"]:where(:focus-visible) {
104
+ border-color: var(--ring);
105
+ outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
106
+ var(--focus-ring-color, var(--ring));
107
+ outline-offset: var(--focus-ring-offset, 0px);
108
+ }
114
109
 
115
- /* ===== Size Variants ===== */
116
- [data-component="date-picker"][data-size="sm"] [data-date-picker-part="trigger"] {
117
- @apply h-8 px-2 py-1 text-xs;
118
- min-width: 160px;
119
- }
110
+ [data-date-picker-part="trigger"]:where(:disabled) {
111
+ @apply opacity-50 cursor-not-allowed;
112
+ }
120
113
 
121
- [data-component="date-picker"][data-size="sm"] [data-date-picker-part="trigger"] svg {
122
- @apply size-3.5;
123
- }
114
+ /* Error state — the root :has() drives the trigger */
115
+ :where([data-component="date-picker"]:has(input:invalid), [data-component="date-picker"]:has(input[aria-invalid="true"])) [data-date-picker-part="trigger"] {
116
+ border-color: var(--destructive);
117
+ }
124
118
 
125
- [data-component="date-picker"][data-size="lg"] [data-date-picker-part="trigger"] {
126
- @apply h-11 px-4 py-3 text-base;
127
- min-width: 240px;
128
- }
119
+ :where([data-component="date-picker"]:has(input:invalid), [data-component="date-picker"]:has(input[aria-invalid="true"])) [data-date-picker-part="trigger"]:where(:focus-visible) {
120
+ --focus-ring-color: var(--destructive);
121
+ }
129
122
 
130
- [data-component="date-picker"][data-size="lg"] [data-date-picker-part="trigger"] svg {
131
- @apply size-5;
132
- }
123
+ /* ===== Popover ===== */
124
+ [data-date-picker-part="popover"] {
125
+ @apply p-0 border;
126
+ border-radius: var(--date-picker-popover-radius, var(--surface-radius, 0.5rem));
127
+ /* A popover is above the page by definition, so it paints --popover */
128
+ background-color: var(--popover, var(--background));
129
+ border-color: var(--border);
130
+ box-shadow: var(--date-picker-popover-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))));
131
+
132
+ /* Reset default popover styles */
133
+ margin: 0;
134
+ overflow: visible;
135
+
136
+ /* Position below trigger */
137
+ position-area: bottom span-right;
138
+ position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
139
+ margin-top: 0.25rem;
133
140
 
134
- /* ===== Full width variant ===== */
135
- [data-component="date-picker"][data-full-width] {
136
- display: block;
137
- width: 100%;
138
- }
141
+ /* Entry animation */
142
+ opacity: 1;
143
+ transform: translateY(0) scale(1);
144
+ transition: opacity 150ms ease-out,
145
+ transform 150ms ease-out;
146
+ }
139
147
 
140
- [data-component="date-picker"][data-full-width] [data-date-picker-part="trigger"] {
141
- width: 100%;
142
- }
148
+ /* Closed state - for exit animation */
149
+ [data-date-picker-part="popover"]:where(:not(:popover-open)) {
150
+ opacity: 0;
151
+ transform: translateY(-0.5rem) scale(0.95);
152
+ }
143
153
 
144
- /* ===== Error state ===== */
145
- [data-component="date-picker"]:has(input:invalid),
146
- [data-component="date-picker"]:has(input[aria-invalid="true"]) {
147
- [data-date-picker-part="trigger"] {
148
- border-color: var(--destructive);
154
+ /* Entry animation starting state */
155
+ @starting-style {
156
+ [data-date-picker-part="popover"]:where(:popover-open) {
157
+ opacity: 0;
158
+ transform: translateY(-0.5rem) scale(0.95);
159
+ }
149
160
  }
150
161
 
151
- [data-date-picker-part="trigger"]:focus-visible {
152
- box-shadow: 0 0 0 2px var(--background),
153
- 0 0 0 4px var(--destructive);
162
+ /* Backdrop */
163
+ [data-date-picker-part="popover"]::backdrop {
164
+ background-color: transparent;
154
165
  }
155
- }
156
166
 
157
- /* ===== Fallback for browsers without anchor positioning ===== */
158
- @supports not (position-area: bottom) {
159
- [data-date-picker-part="popover"] {
160
- position: absolute;
161
- top: 100%;
162
- left: 0;
163
- margin-top: 0.25rem;
167
+ /* ===== Calendar inside popover adjustments ===== */
168
+ :where([data-date-picker-part="popover"]) [data-component="calendar"] {
169
+ border: none;
170
+ box-shadow: var(--elevation-none, none);
164
171
  }
165
- }
166
172
 
167
- /* ===== Dark Mode ===== */
168
- /*
169
- * Dark mode is handled automatically through CSS variables.
170
- * The theme variables change based on the .dark class on html/body.
171
- * No additional dark mode styles needed here.
172
- */
173
+ /* ===== Fallback for browsers without anchor positioning ===== */
174
+ @supports not (position-area: bottom) {
175
+ [data-date-picker-part="popover"] {
176
+ position: absolute;
177
+ top: 100%;
178
+ left: 0;
179
+ margin-top: 0.25rem;
180
+ }
181
+ }
182
+ }
@@ -1,135 +1,168 @@
1
1
  /* ===== Drawer Component Styles ===== */
2
-
3
- /* ===== Provider (Wrapper) ===== */
4
- [data-component="drawer"] {
5
- @apply flex w-full;
6
- }
7
-
8
- /* ===== Drawer Root ===== */
9
- [data-drawer-part="root"] {
10
- @apply block text-foreground;
11
- }
12
-
13
- /* Prevent transitions on initial load */
14
- [data-drawer-part="root"].drawer-loading,
15
- [data-drawer-part="root"].drawer-loading * {
16
- transition: none !important;
17
- }
18
-
19
- /* ===== Drawer Backdrop (overlay) ===== */
20
- [data-drawer-part="backdrop"] {
21
- @apply fixed inset-0 z-40 hidden bg-black opacity-0;
22
- transition: opacity 300ms;
23
- }
24
-
25
- [data-drawer-part="backdrop"][data-state="visible"] {
26
- @apply block opacity-60;
27
- }
28
-
29
- /* ===== Drawer Panel (fixed positioned) ===== */
30
- [data-drawer-part="panel"] {
31
- @apply fixed inset-y-0 z-50 flex h-full w-80 flex-col;
32
- @apply bg-background shadow-lg;
33
- transition: right 300ms ease-in-out, left 300ms ease-in-out;
34
- }
35
-
36
- [data-drawer-part="root"][data-side="left"] [data-drawer-part="panel"] {
37
- @apply left-0;
38
- right: auto;
39
- }
40
-
41
- [data-drawer-part="root"][data-side="right"] [data-drawer-part="panel"] {
42
- @apply right-0;
43
- left: auto;
44
- }
45
-
46
- /* Closed state - slide out */
47
- [data-drawer-part="root"][data-side="left"][data-state="closed"] [data-drawer-part="panel"] {
48
- @apply -translate-x-full;
49
- }
50
-
51
- [data-drawer-part="root"][data-side="right"][data-state="closed"] [data-drawer-part="panel"] {
52
- @apply translate-x-full;
53
- }
54
-
55
- /* ===== Drawer Inner Content Wrapper ===== */
56
- [data-drawer-part="inner"] {
57
- @apply flex h-full w-full flex-col;
2
+ /*
3
+ * Cascade order in this file (full contract documented in form.css):
4
+ * 1. base [data-component="drawer"], [data-drawer-part="…"]
5
+ * 2. sizes — none
6
+ * 3. variants :where([data-drawer-part="root"][data-side="…"]) …
7
+ * 4. states […]:where(:hover, :focus-visible, [data-state="…"])
8
+ * 5. dark — none; dark-mode differences are tokens (see tokens.css)
9
+ * Every rule is flattened to 0,1,0 with :where() and lives in
10
+ * @layer components, so a caller's utility class wins and source order is
11
+ * the only tie-breaker. (Rules whose target is a bare element read 0,1,1 — an
12
+ * element selector cannot be zeroed away.)
13
+ *
14
+ * The one exception is the body scroll lock at the bottom of this file: see the
15
+ * comment there. It is intentionally NOT in the layer.
16
+ */
17
+
18
+ @layer components {
19
+ /* ===== Provider (Wrapper) ===== */
20
+ [data-component="drawer"] {
21
+ @apply flex w-full;
22
+ }
23
+
24
+ /* ===== Drawer Root ===== */
25
+ [data-drawer-part="root"] {
26
+ @apply block text-foreground;
27
+ }
28
+
29
+ /* Prevent transitions on initial load */
30
+ [data-drawer-part="root"]:where(.drawer-loading),
31
+ :where([data-drawer-part="root"].drawer-loading) * {
32
+ transition: none !important;
33
+ }
34
+
35
+ /* ===== Drawer Backdrop (overlay) ===== */
36
+ [data-drawer-part="backdrop"] {
37
+ @apply fixed inset-0 z-40 hidden bg-black opacity-0;
38
+ transition: opacity 300ms;
39
+ }
40
+
41
+ [data-drawer-part="backdrop"]:where([data-state="visible"]) {
42
+ @apply block opacity-60;
43
+ }
44
+
45
+ /* ===== Drawer Panel (fixed positioned) ===== */
46
+ [data-drawer-part="panel"] {
47
+ @apply fixed inset-y-0 z-50 flex h-full w-80 flex-col;
48
+ @apply bg-background;
49
+ box-shadow: var(--drawer-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))));
50
+ transition: right 300ms ease-in-out, left 300ms ease-in-out;
51
+ }
52
+
53
+ :where([data-drawer-part="root"][data-side="left"]) [data-drawer-part="panel"] {
54
+ @apply left-0;
55
+ right: auto;
56
+ }
57
+
58
+ :where([data-drawer-part="root"][data-side="right"]) [data-drawer-part="panel"] {
59
+ @apply right-0;
60
+ left: auto;
61
+ }
62
+
63
+ /* Closed state - slide out */
64
+ :where([data-drawer-part="root"][data-side="left"][data-state="closed"]) [data-drawer-part="panel"] {
65
+ @apply -translate-x-full;
66
+ }
67
+
68
+ :where([data-drawer-part="root"][data-side="right"][data-state="closed"]) [data-drawer-part="panel"] {
69
+ @apply translate-x-full;
70
+ }
71
+
72
+ /* ===== Drawer Inner Content Wrapper ===== */
73
+ [data-drawer-part="inner"] {
74
+ @apply flex h-full w-full flex-col;
75
+ }
76
+
77
+ /* ===== Drawer Header ===== */
78
+ [data-drawer-part="header"] {
79
+ @apply flex shrink-0 items-center justify-between gap-2 border-b px-4 py-3;
80
+ border-color: var(--border);
81
+ }
82
+
83
+ [data-drawer-part="header-content"] {
84
+ @apply flex-1;
85
+ }
86
+
87
+ /* ===== Drawer Content (scrollable area) ===== */
88
+ [data-drawer-part="content"] {
89
+ @apply flex min-h-0 flex-1 flex-col gap-2 overflow-auto p-4;
90
+ }
91
+
92
+ /* ===== Drawer Footer ===== */
93
+ [data-drawer-part="footer"] {
94
+ @apply flex shrink-0 flex-col gap-2 border-t p-4;
95
+ border-color: var(--border);
96
+ }
97
+
98
+ /* ===== Drawer Trigger ===== */
99
+ [data-drawer-part="trigger"]:where(:hover) {
100
+ background-color: var(--accent-hover, var(--accent));
101
+ color: var(--accent-foreground);
102
+ }
103
+
104
+ /* ===== Drawer Title ===== */
105
+ [data-drawer-part="title"] {
106
+ @apply text-lg font-semibold;
107
+ }
108
+
109
+ /* ===== Drawer Description ===== */
110
+ [data-drawer-part="description"] {
111
+ @apply text-sm text-muted-foreground;
112
+ }
113
+
114
+ /* ===== Drawer Close Button ===== */
115
+ [data-drawer-part="close"] {
116
+ @apply inline-flex items-center justify-center p-1;
117
+ @apply transition-colors;
118
+ border-radius: var(--drawer-close-radius, var(--control-radius, 0.375rem));
119
+ color: var(--foreground);
120
+ }
121
+
122
+ [data-drawer-part="close"] svg {
123
+ @apply size-4;
124
+ }
125
+
126
+ [data-drawer-part="close"]:where(:hover) {
127
+ background-color: var(--accent-hover, var(--accent));
128
+ color: var(--accent-foreground);
129
+ }
130
+
131
+ /* outline, not a ring: the drawer panel clips box-shadow rings */
132
+ [data-drawer-part="close"]:where(:focus-visible) {
133
+ outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
134
+ var(--focus-ring-color, var(--ring));
135
+ outline-offset: var(--focus-ring-offset, 0px);
136
+ }
137
+
138
+ /* ===== Drawer Section ===== */
139
+ [data-drawer-part="section"] {
140
+ @apply flex flex-col gap-2;
141
+ }
142
+
143
+ /* ===== Drawer Separator ===== */
144
+ [data-drawer-part="separator"] {
145
+ @apply mx-4 my-2 w-auto;
146
+ background-color: var(--border);
147
+ }
58
148
  }
59
149
 
60
150
  /* ===== 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. */
151
+ /*
152
+ * Shared by drawer_controller and sidebar_controller: the controllers set
153
+ * the data attribute and the --maquina-scroll-lock-top offset on <body>;
154
+ * all styling lives here.
155
+ *
156
+ * PUBLIC CONTRACT — this is the only engine rule outside @layer components.
157
+ * It is not component appearance, it is a functional lock: if a host's own
158
+ * unlayered `body { position: … }` beat it, the page would scroll behind an
159
+ * open drawer and the restored scroll position would be wrong. Keeping it
160
+ * unlayered means it wins the way it did before 0.6.0. Nothing else in the
161
+ * engine claims that privilege.
162
+ */
64
163
  body[data-maquina-scroll-locked] {
65
164
  position: fixed;
66
165
  top: var(--maquina-scroll-lock-top, 0px);
67
166
  width: 100%;
68
167
  overflow: hidden;
69
168
  }
70
-
71
- /* ===== Drawer Header ===== */
72
- [data-drawer-part="header"] {
73
- @apply flex shrink-0 items-center justify-between gap-2 border-b px-4 py-3;
74
- border-color: var(--border);
75
- }
76
-
77
- [data-drawer-part="header-content"] {
78
- @apply flex-1;
79
- }
80
-
81
- /* ===== Drawer Content (scrollable area) ===== */
82
- [data-drawer-part="content"] {
83
- @apply flex min-h-0 flex-1 flex-col gap-2 overflow-auto p-4;
84
- }
85
-
86
- /* ===== Drawer Footer ===== */
87
- [data-drawer-part="footer"] {
88
- @apply flex shrink-0 flex-col gap-2 border-t p-4;
89
- border-color: var(--border);
90
- }
91
-
92
- /* ===== Drawer Trigger ===== */
93
-
94
- [data-drawer-part="trigger"]:hover {
95
- background-color: var(--accent);
96
- color: var(--accent-foreground);
97
- }
98
-
99
- /* ===== Drawer Title ===== */
100
- [data-drawer-part="title"] {
101
- @apply text-lg font-semibold;
102
- }
103
-
104
- /* ===== Drawer Description ===== */
105
- [data-drawer-part="description"] {
106
- @apply text-sm text-muted-foreground;
107
- }
108
-
109
- /* ===== Drawer Close Button ===== */
110
- [data-drawer-part="close"] {
111
- @apply inline-flex items-center justify-center rounded-md p-1;
112
- @apply outline-hidden ring-ring focus-visible:ring-2;
113
- @apply transition-colors;
114
- color: var(--foreground);
115
- }
116
-
117
- [data-drawer-part="close"]:hover {
118
- background-color: var(--accent);
119
- color: var(--accent-foreground);
120
- }
121
-
122
- [data-drawer-part="close"] svg {
123
- @apply size-4;
124
- }
125
-
126
- /* ===== Drawer Section ===== */
127
- [data-drawer-part="section"] {
128
- @apply flex flex-col gap-2;
129
- }
130
-
131
- /* ===== Drawer Separator ===== */
132
- [data-drawer-part="separator"] {
133
- @apply mx-4 my-2 w-auto;
134
- background-color: var(--border);
135
- }