maquina-components 0.5.0 → 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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +79 -3
  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 +185 -147
  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 +215 -163
  9. data/app/assets/stylesheets/date_picker.css +144 -134
  10. data/app/assets/stylesheets/drawer.css +166 -118
  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 +178 -0
  16. data/app/assets/stylesheets/pagination.css +130 -125
  17. data/app/assets/stylesheets/separator.css +32 -0
  18. data/app/assets/stylesheets/sidebar.css +448 -390
  19. data/app/assets/stylesheets/stats.css +111 -0
  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 -12
  24. data/app/assets/tailwind/maquina_components_engine/tokens.css +101 -0
  25. data/app/helpers/maquina_components/combobox_helper.rb +34 -25
  26. data/app/helpers/maquina_components/components_helper.rb +57 -0
  27. data/app/helpers/maquina_components/dropdown_menu_helper.rb +12 -12
  28. data/app/helpers/maquina_components/icons_helper.rb +84 -2
  29. data/app/javascript/controllers/breadcrumb_controller.js +39 -30
  30. data/app/javascript/controllers/combobox_controller.js +9 -0
  31. data/app/javascript/controllers/drawer_controller.js +43 -9
  32. data/app/javascript/controllers/drawer_trigger_controller.js +55 -4
  33. data/app/javascript/controllers/menu_button_controller.js +107 -29
  34. data/app/javascript/controllers/sidebar_controller.js +6 -8
  35. data/app/javascript/controllers/sidebar_trigger_controller.js +33 -4
  36. data/app/javascript/controllers/toaster_controller.js +12 -0
  37. data/app/views/components/_alert.html.erb +3 -1
  38. data/app/views/components/_badge.html.erb +7 -1
  39. data/app/views/components/_breadcrumbs.html.erb +1 -1
  40. data/app/views/components/_calendar.html.erb +1 -1
  41. data/app/views/components/_card.html.erb +1 -1
  42. data/app/views/components/_combobox.html.erb +3 -2
  43. data/app/views/components/_date_picker.html.erb +4 -3
  44. data/app/views/components/_drawer.html.erb +8 -2
  45. data/app/views/components/_dropdown.html.erb +21 -24
  46. data/app/views/components/_dropdown_menu.html.erb +1 -1
  47. data/app/views/components/_empty.html.erb +1 -1
  48. data/app/views/components/_header.html.erb +1 -1
  49. data/app/views/components/_label.html.erb +14 -0
  50. data/app/views/components/_menu_button.html.erb +29 -34
  51. data/app/views/components/_pagination.html.erb +1 -1
  52. data/app/views/components/_separator.html.erb +9 -10
  53. data/app/views/components/_sidebar.html.erb +1 -1
  54. data/app/views/components/_simple_table.html.erb +3 -1
  55. data/app/views/components/_table.html.erb +3 -3
  56. data/app/views/components/_toast.html.erb +6 -1
  57. data/app/views/components/_toaster.html.erb +1 -1
  58. data/app/views/components/_toggle_group.html.erb +1 -1
  59. data/app/views/components/alert/_description.html.erb +1 -1
  60. data/app/views/components/alert/_title.html.erb +1 -1
  61. data/app/views/components/breadcrumbs/_ellipsis.html.erb +1 -1
  62. data/app/views/components/breadcrumbs/_item.html.erb +1 -1
  63. data/app/views/components/breadcrumbs/_link.html.erb +1 -1
  64. data/app/views/components/breadcrumbs/_list.html.erb +1 -1
  65. data/app/views/components/breadcrumbs/_page.html.erb +1 -1
  66. data/app/views/components/breadcrumbs/_separator.html.erb +1 -1
  67. data/app/views/components/calendar/_header.html.erb +1 -1
  68. data/app/views/components/calendar/_week.html.erb +5 -13
  69. data/app/views/components/card/_action.html.erb +1 -1
  70. data/app/views/components/card/_content.html.erb +1 -1
  71. data/app/views/components/card/_description.html.erb +1 -1
  72. data/app/views/components/card/_footer.html.erb +1 -1
  73. data/app/views/components/card/_header.html.erb +1 -1
  74. data/app/views/components/card/_title.html.erb +1 -1
  75. data/app/views/components/combobox/_content.html.erb +1 -1
  76. data/app/views/components/combobox/_empty.html.erb +3 -3
  77. data/app/views/components/combobox/_group.html.erb +1 -1
  78. data/app/views/components/combobox/_input.html.erb +1 -1
  79. data/app/views/components/combobox/_label.html.erb +1 -1
  80. data/app/views/components/combobox/_list.html.erb +1 -1
  81. data/app/views/components/combobox/_option.html.erb +1 -1
  82. data/app/views/components/combobox/_separator.html.erb +1 -1
  83. data/app/views/components/combobox/_trigger.html.erb +1 -1
  84. data/app/views/components/drawer/_close.html.erb +2 -2
  85. data/app/views/components/drawer/_content.html.erb +1 -1
  86. data/app/views/components/drawer/_description.html.erb +6 -0
  87. data/app/views/components/drawer/_footer.html.erb +1 -1
  88. data/app/views/components/drawer/_header.html.erb +3 -3
  89. data/app/views/components/drawer/_provider.html.erb +14 -5
  90. data/app/views/components/drawer/_section.html.erb +7 -0
  91. data/app/views/components/drawer/_separator.html.erb +11 -0
  92. data/app/views/components/drawer/_title.html.erb +6 -0
  93. data/app/views/components/drawer/_trigger.html.erb +24 -7
  94. data/app/views/components/dropdown_menu/_content.html.erb +1 -1
  95. data/app/views/components/dropdown_menu/_group.html.erb +1 -1
  96. data/app/views/components/dropdown_menu/_item.html.erb +1 -1
  97. data/app/views/components/dropdown_menu/_label.html.erb +3 -7
  98. data/app/views/components/dropdown_menu/_separator.html.erb +1 -1
  99. data/app/views/components/dropdown_menu/_shortcut.html.erb +3 -7
  100. data/app/views/components/dropdown_menu/_trigger.html.erb +1 -1
  101. data/app/views/components/empty/_content.html.erb +1 -1
  102. data/app/views/components/empty/_description.html.erb +3 -7
  103. data/app/views/components/empty/_header.html.erb +1 -1
  104. data/app/views/components/empty/_media.html.erb +5 -3
  105. data/app/views/components/empty/_title.html.erb +3 -7
  106. data/app/views/components/pagination/_content.html.erb +1 -1
  107. data/app/views/components/pagination/_item.html.erb +1 -1
  108. data/app/views/components/pagination/_link.html.erb +1 -1
  109. data/app/views/components/pagination/_next.html.erb +1 -1
  110. data/app/views/components/pagination/_previous.html.erb +1 -1
  111. data/app/views/components/sidebar/_content.html.erb +1 -1
  112. data/app/views/components/sidebar/_footer.html.erb +1 -1
  113. data/app/views/components/sidebar/_group.html.erb +1 -1
  114. data/app/views/components/sidebar/_group_action.html.erb +26 -0
  115. data/app/views/components/sidebar/_header.html.erb +1 -1
  116. data/app/views/components/sidebar/_inset.html.erb +1 -1
  117. data/app/views/components/sidebar/_menu.html.erb +1 -1
  118. data/app/views/components/sidebar/_menu_action.html.erb +28 -0
  119. data/app/views/components/sidebar/_menu_badge.html.erb +10 -0
  120. data/app/views/components/sidebar/_menu_button.html.erb +4 -3
  121. data/app/views/components/sidebar/_menu_item.html.erb +1 -1
  122. data/app/views/components/sidebar/_menu_link.html.erb +7 -9
  123. data/app/views/components/sidebar/_provider.html.erb +1 -1
  124. data/app/views/components/sidebar/_separator.html.erb +12 -0
  125. data/app/views/components/sidebar/_trigger.html.erb +1 -1
  126. data/app/views/components/stats/_stats_card.html.erb +32 -92
  127. data/app/views/components/stats/_stats_grid.html.erb +18 -24
  128. data/app/views/components/table/_body.html.erb +1 -1
  129. data/app/views/components/table/_caption.html.erb +3 -3
  130. data/app/views/components/table/_cell.html.erb +3 -3
  131. data/app/views/components/table/_footer.html.erb +1 -1
  132. data/app/views/components/table/_head.html.erb +3 -3
  133. data/app/views/components/table/_header.html.erb +1 -1
  134. data/app/views/components/table/_row.html.erb +1 -1
  135. data/app/views/components/toast/_action.html.erb +1 -1
  136. data/app/views/components/toast/_description.html.erb +1 -1
  137. data/app/views/components/toast/_title.html.erb +1 -1
  138. data/app/views/components/toggle_group/_item.html.erb +1 -1
  139. data/lib/generators/maquina_components/install/install_generator.rb +36 -1
  140. data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +6 -0
  141. data/lib/generators/maquina_components/install/templates/shape_state_tokens.css.tt +76 -0
  142. data/lib/generators/maquina_components/install/templates/theme.css.tt +10 -3
  143. data/lib/maquina-components.rb +34 -1
  144. data/lib/maquina_components/doctor.rb +280 -0
  145. data/lib/maquina_components/engine.rb +7 -3
  146. data/lib/maquina_components/version.rb +1 -1
  147. data/lib/tasks/maquina_components_tasks.rake +16 -4
  148. metadata +46 -3
  149. data/app/assets/stylesheets/maquina_components.css +0 -178
@@ -1,145 +1,150 @@
1
1
  /* ===== Badge Component Styles ===== */
2
- /*
2
+ /*
3
3
  * Badge component for status indicators, tags, counts, and labels.
4
4
  * Uses data attributes for styling to avoid inline utility classes.
5
5
  * Fully compatible with dark mode and supports icons.
6
+ *
7
+ * Cascade order in this file (full contract documented in form.css):
8
+ * 1. base [data-component="badge"]
9
+ * 2. sizes [data-component="badge"]:where([data-size="…"])
10
+ * 3. variants [data-component="badge"]:where([data-variant="…"])
11
+ * 4. states :where(:hover, :focus-visible, :active, :disabled)
12
+ * 5. dark — none; dark-mode differences are tokens (see tokens.css)
13
+ * Every rule is flattened to 0,1,0 with :where() and lives in
14
+ * @layer components, so a caller's utility class wins and source order is
15
+ * the only tie-breaker. (Rules whose target is a bare element or a
16
+ * pseudo-element read 0,1,1 — an element selector cannot be zeroed away.)
6
17
  */
7
18
 
8
- /* ===== Base Badge Styles ===== */
9
- [data-component="badge"] {
10
- /* Layout */
11
- display: inline-flex;
12
- align-items: center;
13
- white-space: nowrap;
14
- @apply gap-1 rounded-md border border-transparent font-medium leading-none;
15
-
16
- /* Transitions */
17
- @apply transition-[background-color,border-color,color,opacity] duration-150;
18
- }
19
-
20
- /* ===== Icon Support ===== */
21
- [data-component="badge"] svg {
22
- /* Scales with font size using em */
23
- width: 0.875em;
24
- height: 0.875em;
25
- @apply shrink-0 pointer-events-none;
26
- }
27
-
28
- /* ===== Size Variants ===== */
29
-
30
- /* Small size - compact badges for inline use */
31
- [data-component="badge"][data-size="sm"] {
32
- @apply text-xs px-2 py-0.5 h-5;
33
- }
34
-
35
- /* Medium size - default, balanced size */
36
- [data-component="badge"][data-size="md"] {
37
- @apply text-sm px-2.5 py-1 h-6;
38
- }
39
-
40
- /* Large size - prominent badges */
41
- [data-component="badge"][data-size="lg"] {
42
- @apply text-sm px-3 py-1.5 h-7;
43
- }
44
-
45
- /* ===== Visual Variants ===== */
46
-
47
- /* Default variant - muted appearance */
48
- [data-component="badge"][data-variant="default"] {
49
- background-color: var(--muted);
50
- color: var(--muted-foreground);
51
- }
52
-
53
- [data-component="badge"][data-variant="default"]:hover {
54
- @apply opacity-90;
55
- }
56
-
57
- /* Primary variant - brand color */
58
- [data-component="badge"][data-variant="primary"] {
59
- background-color: var(--primary);
60
- color: var(--primary-foreground);
61
- }
62
-
63
- [data-component="badge"][data-variant="primary"]:hover {
64
- @apply opacity-90;
65
- }
66
-
67
- /* Secondary variant - subtle appearance */
68
- [data-component="badge"][data-variant="secondary"] {
69
- background-color: var(--secondary);
70
- color: var(--secondary-foreground);
71
- }
72
-
73
- [data-component="badge"][data-variant="secondary"]:hover {
74
- @apply opacity-90;
75
- }
76
-
77
- /* Destructive variant - errors, deletions */
78
- [data-component="badge"][data-variant="destructive"] {
79
- background-color: var(--destructive);
80
- color: var(--destructive-foreground);
19
+ @layer components {
20
+ /* ===== Base Badge Styles ===== */
21
+ [data-component="badge"] {
22
+ /* Layout */
23
+ display: inline-flex;
24
+ align-items: center;
25
+ white-space: nowrap;
26
+ @apply gap-1 border border-transparent leading-none;
27
+ border-radius: var(--badge-radius, var(--control-radius, 0.375rem));
28
+ font-weight: var(--label-weight, 500);
29
+
30
+ /* Transitions */
31
+ @apply transition-[background-color,border-color,color,opacity] duration-150;
32
+ }
33
+
34
+ /* ===== Icon Support ===== */
35
+ [data-component="badge"] svg {
36
+ /* Scales with font size using em */
37
+ width: 0.875em;
38
+ height: 0.875em;
39
+ @apply shrink-0 pointer-events-none;
40
+ }
41
+
42
+ /* ===== Size Variants ===== */
43
+
44
+ /* Small size - compact badges for inline use */
45
+ [data-component="badge"]:where([data-size="sm"]) {
46
+ @apply text-xs px-2 py-0.5 h-5;
47
+ }
48
+
49
+ /* Medium size - default, balanced size */
50
+ [data-component="badge"]:where([data-size="md"]) {
51
+ @apply text-sm px-2.5 py-1 h-6;
52
+ }
53
+
54
+ /* Large size - prominent badges */
55
+ [data-component="badge"]:where([data-size="lg"]) {
56
+ @apply text-sm px-3 py-1.5 h-7;
57
+ }
58
+
59
+ /* ===== Visual Variants ===== */
60
+
61
+ /* Default variant - muted appearance */
62
+ [data-component="badge"]:where([data-variant="default"]) {
63
+ background-color: var(--muted);
64
+ color: var(--muted-foreground);
65
+ }
66
+
67
+ /* Primary variant - brand color */
68
+ [data-component="badge"]:where([data-variant="primary"]) {
69
+ background-color: var(--primary);
70
+ color: var(--primary-foreground);
71
+ }
72
+
73
+ /* Secondary variant - subtle appearance */
74
+ [data-component="badge"]:where([data-variant="secondary"]) {
75
+ background-color: var(--secondary);
76
+ color: var(--secondary-foreground);
77
+ }
78
+
79
+ /* Destructive variant - errors, deletions */
80
+ [data-component="badge"]:where([data-variant="destructive"]) {
81
+ background-color: var(--destructive);
82
+ color: var(--destructive-foreground);
83
+ }
84
+
85
+ /* Success variant - positive status */
86
+ [data-component="badge"]:where([data-variant="success"]) {
87
+ background-color: var(--success, oklch(0.627 0.194 149.214));
88
+ color: var(--success-foreground, oklch(0.985 0 0));
89
+ }
90
+
91
+ /* Warning variant - caution status */
92
+ [data-component="badge"]:where([data-variant="warning"]) {
93
+ background-color: var(--warning, oklch(0.769 0.188 70.08));
94
+ color: var(--warning-foreground, oklch(0.216 0.006 56.043));
95
+ }
96
+
97
+ /* Info variant - informational status.
98
+ --info has no engine default on purpose: undefined falls back to the neutral
99
+ default treatment, never to --primary (which would make an informational
100
+ badge read as a brand call to action). */
101
+ [data-component="badge"]:where([data-variant="info"]) {
102
+ background-color: var(--info, var(--muted));
103
+ color: var(--info-foreground, var(--muted-foreground));
104
+ }
105
+
106
+ /* Outline variant - transparent with border */
107
+ [data-component="badge"]:where([data-variant="outline"]) {
108
+ @apply bg-transparent;
109
+ color: var(--foreground);
110
+ border-color: var(--border);
111
+ }
112
+
113
+ /* ===== Interactive States (when used as link/button) ===== */
114
+
115
+ /* Hover - solid variants just soften */
116
+ [data-component="badge"]:where(
117
+ [data-variant="default"]:hover,
118
+ [data-variant="primary"]:hover,
119
+ [data-variant="secondary"]:hover,
120
+ [data-variant="destructive"]:hover,
121
+ [data-variant="success"]:hover,
122
+ [data-variant="warning"]:hover,
123
+ [data-variant="info"]:hover
124
+ ) {
125
+ @apply opacity-90;
126
+ }
127
+
128
+ /* Hover - the outline variant fills instead */
129
+ [data-component="badge"]:where([data-variant="outline"]:hover) {
130
+ background-color: var(--accent-hover, var(--accent));
131
+ color: var(--accent-foreground);
132
+ }
133
+
134
+ /* Focus state for keyboard navigation */
135
+ [data-component="badge"]:where(a:focus-visible, button:focus-visible) {
136
+ outline: var(--focus-ring-width, 3px) var(--focus-ring-style, solid)
137
+ var(--focus-ring-color, var(--ring));
138
+ outline-offset: var(--focus-ring-offset, 0px);
139
+ }
140
+
141
+ /* Active/pressed state */
142
+ [data-component="badge"]:where(a:active, button:active) {
143
+ @apply scale-[0.98];
144
+ }
145
+
146
+ /* Disabled state (if used as button) */
147
+ [data-component="badge"]:where(button:disabled) {
148
+ @apply opacity-50 cursor-not-allowed pointer-events-none;
149
+ }
81
150
  }
82
-
83
- [data-component="badge"][data-variant="destructive"]:hover {
84
- @apply opacity-90;
85
- }
86
-
87
- /* Success variant - positive status */
88
- [data-component="badge"][data-variant="success"] {
89
- background-color: var(--success);
90
- color: var(--success-foreground);
91
- }
92
-
93
- [data-component="badge"][data-variant="success"]:hover {
94
- @apply opacity-90;
95
- }
96
-
97
- /* Warning variant - caution status */
98
- [data-component="badge"][data-variant="warning"] {
99
- background-color: var(--warning);
100
- color: var(--warning-foreground);
101
- }
102
-
103
- [data-component="badge"][data-variant="warning"]:hover {
104
- @apply opacity-90;
105
- }
106
-
107
- /* Outline variant - transparent with border */
108
- [data-component="badge"][data-variant="outline"] {
109
- @apply bg-transparent;
110
- color: var(--foreground);
111
- border-color: var(--border);
112
- }
113
-
114
- [data-component="badge"][data-variant="outline"]:hover {
115
- background-color: var(--accent);
116
- color: var(--accent-foreground);
117
- }
118
-
119
- /* ===== Interactive States (when used as link/button) ===== */
120
-
121
- /* Focus state for keyboard navigation */
122
- a[data-component="badge"]:focus-visible,
123
- button[data-component="badge"]:focus-visible {
124
- @apply outline-none;
125
- box-shadow: 0 0 0 2px var(--background),
126
- 0 0 0 4px var(--border);
127
- }
128
-
129
- /* Active/pressed state */
130
- a[data-component="badge"]:active,
131
- button[data-component="badge"]:active {
132
- @apply scale-[0.98];
133
- }
134
-
135
- /* Disabled state (if used as button) */
136
- button[data-component="badge"]:disabled {
137
- @apply opacity-50 cursor-not-allowed pointer-events-none;
138
- }
139
-
140
- /* ===== Dark Mode ===== */
141
- /*
142
- * Dark mode is handled automatically through CSS variables.
143
- * The theme variables change based on the .dark class on html/body.
144
- * No additional dark mode styles needed here.
145
- */
@@ -1,186 +1,224 @@
1
1
  /* ===== Breadcrumbs Component Styles ===== */
2
2
  /* Comprehensive CSS using data-attribute selectors */
3
3
  /* Uses @apply for theme-customizable properties */
4
+ /*
5
+ * Cascade order in this file (full contract documented in form.css):
6
+ * 1. base [data-component="breadcrumbs"], [data-breadcrumb-part="…"]
7
+ * 2. sizes :where([data-component="breadcrumbs"][data-size="sm"]) …
8
+ * 3. variants :where([data-component="breadcrumbs"][data-separator="…"]) …
9
+ * 4. states […]:where(:hover, :focus-visible, [data-state="open"], .hidden)
10
+ * 5. dark — none; dark-mode differences are tokens (see tokens.css)
11
+ * Every rule is flattened to 0,1,0 with :where() and lives in
12
+ * @layer components, so a caller's utility class wins and source order is
13
+ * the only tie-breaker. (Rules whose target is a bare element or a
14
+ * pseudo-element read 0,1,1 — an element selector cannot be zeroed away.)
15
+ *
16
+ * Imported LAST, after dropdown_menu.css: the overflow popover below has to
17
+ * beat dropdown_menu.css's `position: absolute`, and since every rule is now a
18
+ * flat 0,1,0 that is decided by import order alone (it used to lean on a
19
+ * doubled attribute selector). See engine.css.
20
+ */
21
+
22
+ @layer components {
23
+ /* ===== Breadcrumbs Container (nav) ===== */
24
+ [data-component="breadcrumbs"] {
25
+ /* Container has no default styles - allows flexible placement */
26
+ }
4
27
 
5
- /* ===== Breadcrumbs Container (nav) ===== */
6
- [data-component="breadcrumbs"] {
7
- /* Container has no default styles - allows flexible placement */
8
- }
9
-
10
- /* ===== Breadcrumbs List ===== */
11
- [data-breadcrumb-part="list"] {
12
- @apply flex flex-wrap items-center gap-1.5;
13
- @apply text-sm break-words;
14
- list-style: none;
15
- padding: 0;
16
- margin: 0;
17
- }
28
+ /* ===== Breadcrumbs List ===== */
29
+ [data-breadcrumb-part="list"] {
30
+ @apply flex flex-wrap items-center gap-1.5;
31
+ @apply text-sm break-words;
32
+ list-style: none;
33
+ padding: 0;
34
+ margin: 0;
35
+ }
18
36
 
19
- /* Compact variant */
20
- [data-component="breadcrumbs"][data-size="sm"] [data-breadcrumb-part="list"] {
21
- @apply gap-1 text-xs;
22
- }
37
+ /* Compact variant */
38
+ :where([data-component="breadcrumbs"][data-size="sm"]) [data-breadcrumb-part="list"] {
39
+ @apply gap-1 text-xs;
40
+ }
23
41
 
24
- /* ===== Breadcrumb Item ===== */
25
- [data-breadcrumb-part="item"] {
26
- @apply inline-flex items-center;
27
- }
42
+ /* ===== Breadcrumb Item ===== */
43
+ [data-breadcrumb-part="item"] {
44
+ @apply inline-flex items-center;
45
+ }
28
46
 
29
- /* Hidden state for responsive collapsing */
30
- [data-breadcrumb-part="item"].hidden {
31
- @apply hidden;
32
- }
47
+ /* Hidden state for responsive collapsing */
48
+ [data-breadcrumb-part="item"]:where(.hidden) {
49
+ @apply hidden;
50
+ }
33
51
 
34
- /* ===== Breadcrumb Link ===== */
35
- [data-breadcrumb-part="link"] {
36
- @apply inline-flex items-center;
37
- @apply text-sm font-medium;
38
- @apply underline-offset-4;
39
- @apply transition-colors;
40
- color: var(--muted-foreground);
41
- }
52
+ /* ===== Breadcrumb Link ===== */
53
+ [data-breadcrumb-part="link"] {
54
+ @apply inline-flex items-center;
55
+ @apply text-sm;
56
+ font-weight: var(--label-weight, 500);
57
+ @apply underline-offset-4;
58
+ @apply transition-colors;
59
+ color: var(--muted-foreground);
60
+ }
42
61
 
43
- [data-breadcrumb-part="link"]:hover {
44
- color: var(--foreground);
45
- @apply underline;
46
- }
62
+ /* Link with icon */
63
+ [data-breadcrumb-part="link"] svg {
64
+ @apply size-3.5 shrink-0;
65
+ }
47
66
 
48
- [data-breadcrumb-part="link"]:focus-visible {
49
- @apply outline-none underline;
50
- color: var(--foreground);
51
- }
67
+ [data-breadcrumb-part="link"] svg:where(:first-child) {
68
+ @apply mr-1.5;
69
+ }
52
70
 
53
- /* Link with icon */
54
- [data-breadcrumb-part="link"] svg {
55
- @apply size-3.5 shrink-0;
56
- }
71
+ [data-breadcrumb-part="link"]:where(:hover) {
72
+ color: var(--foreground);
73
+ @apply underline;
74
+ }
57
75
 
58
- [data-breadcrumb-part="link"] svg:first-child {
59
- @apply mr-1.5;
60
- }
76
+ [data-breadcrumb-part="link"]:where(:focus-visible) {
77
+ @apply outline-none underline;
78
+ color: var(--foreground);
79
+ }
61
80
 
62
- /* ===== Breadcrumb Page (current) ===== */
63
- [data-breadcrumb-part="page"] {
64
- @apply inline-flex items-center;
65
- @apply text-sm font-medium;
66
- color: var(--foreground);
67
- }
81
+ /* ===== Breadcrumb Page (current) ===== */
82
+ [data-breadcrumb-part="page"] {
83
+ @apply inline-flex items-center;
84
+ @apply text-sm;
85
+ font-weight: var(--label-weight, 500);
86
+ color: var(--foreground);
87
+ }
68
88
 
69
- /* Page with icon */
70
- [data-breadcrumb-part="page"] svg {
71
- @apply size-3.5 shrink-0;
72
- }
89
+ /* Page with icon */
90
+ [data-breadcrumb-part="page"] svg {
91
+ @apply size-3.5 shrink-0;
92
+ }
73
93
 
74
- [data-breadcrumb-part="page"] svg:first-child {
75
- @apply mr-1.5;
76
- }
94
+ [data-breadcrumb-part="page"] svg:where(:first-child) {
95
+ @apply mr-1.5;
96
+ }
77
97
 
78
- /* ===== Breadcrumb Separator ===== */
79
- [data-breadcrumb-part="separator"] {
80
- @apply inline-flex items-center;
81
- color: var(--muted-foreground);
82
- }
98
+ /* ===== Breadcrumb Separator ===== */
99
+ [data-breadcrumb-part="separator"] {
100
+ @apply inline-flex items-center;
101
+ color: var(--muted-foreground);
102
+ }
83
103
 
84
- [data-breadcrumb-part="separator"] svg {
85
- @apply size-3.5 shrink-0;
86
- }
104
+ [data-breadcrumb-part="separator"] svg {
105
+ @apply size-3.5 shrink-0;
106
+ }
87
107
 
88
- /* Hidden state for responsive collapsing */
89
- [data-breadcrumb-part="separator"].hidden {
90
- @apply hidden;
91
- }
108
+ /* Hidden state for responsive collapsing */
109
+ [data-breadcrumb-part="separator"]:where(.hidden) {
110
+ @apply hidden;
111
+ }
92
112
 
93
- /* ===== Breadcrumb Ellipsis ===== */
94
- [data-breadcrumb-part="ellipsis"] {
95
- @apply inline-flex size-9 items-center justify-center;
96
- color: var(--muted-foreground);
97
- }
113
+ /* ===== Breadcrumb Ellipsis ===== */
114
+ [data-breadcrumb-part="ellipsis"] {
115
+ @apply inline-flex size-9 items-center justify-center;
116
+ color: var(--muted-foreground);
117
+ cursor: pointer;
118
+ }
98
119
 
99
- [data-breadcrumb-part="ellipsis"] svg {
100
- @apply size-4 shrink-0;
101
- }
120
+ [data-breadcrumb-part="ellipsis"] svg {
121
+ @apply size-4 shrink-0;
122
+ }
102
123
 
103
- /* Ellipsis as dropdown trigger */
104
- [data-breadcrumb-part="ellipsis"][data-state="open"],
105
- [data-breadcrumb-part="ellipsis"]:hover {
106
- color: var(--foreground);
107
- }
124
+ /* Ellipsis as dropdown trigger */
125
+ [data-breadcrumb-part="ellipsis"]:where([data-state="open"], :hover) {
126
+ color: var(--foreground);
127
+ }
108
128
 
109
- /* ===== Responsive Behavior ===== */
110
- /* Force nowrap when responsive controller is active so scrollWidth detects overflow */
111
- [data-controller="breadcrumb"] [data-breadcrumb-part="list"] {
112
- @apply flex-nowrap overflow-hidden;
113
- }
129
+ /* ===== Overflow Dropdown (top-layer popover) ===== */
130
+ /* Visual styling comes from dropdown_menu.css via data-dropdown-menu-part.
131
+ Anchor positioning places the popover in modern browsers;
132
+ breadcrumb_controller falls back to measured coordinates elsewhere. */
133
+ @supports (anchor-name: --a) and (position-area: block-end) {
134
+ [data-breadcrumb-part="ellipsis"] {
135
+ anchor-name: --maquina-breadcrumbs;
136
+ }
137
+
138
+ [data-breadcrumb-part="dropdown"]:where([data-dropdown-menu-part="content"]) {
139
+ position: fixed;
140
+ position-anchor: --maquina-breadcrumbs;
141
+ position-area: block-end span-inline-end;
142
+ margin-block-start: 4px;
143
+ position-try-fallbacks: flip-block;
144
+ }
145
+ }
114
146
 
115
- /* Items must not shrink or wrap text, otherwise flex compresses them and scrollWidth never exceeds clientWidth */
116
- [data-controller="breadcrumb"] [data-breadcrumb-part="item"],
117
- [data-controller="breadcrumb"] [data-breadcrumb-part="separator"] {
118
- @apply shrink-0 whitespace-nowrap;
119
- }
147
+ /* ===== Responsive Behavior ===== */
148
+ /* Force nowrap when responsive controller is active so scrollWidth detects overflow */
149
+ :where([data-controller="breadcrumb"]) [data-breadcrumb-part="list"] {
150
+ @apply flex-nowrap overflow-hidden;
151
+ }
120
152
 
121
- /* Allow the last item (current page) to truncate gracefully instead of hard-clipping */
122
- [data-controller="breadcrumb"] [data-breadcrumb-part="item"]:last-child {
123
- @apply shrink min-w-0 overflow-hidden;
124
- text-overflow: ellipsis;
125
- }
153
+ /* Items must not shrink or wrap text, otherwise flex compresses them and scrollWidth never exceeds clientWidth */
154
+ :where([data-controller="breadcrumb"]) [data-breadcrumb-part="item"],
155
+ :where([data-controller="breadcrumb"]) [data-breadcrumb-part="separator"] {
156
+ @apply shrink-0 whitespace-nowrap;
157
+ }
126
158
 
127
- [data-controller="breadcrumb"] [data-breadcrumb-part="item"]:last-child [data-breadcrumb-part="page"],
128
- [data-controller="breadcrumb"] [data-breadcrumb-part="item"]:last-child [data-breadcrumb-part="link"] {
129
- @apply overflow-hidden;
130
- text-overflow: ellipsis;
131
- }
159
+ /* Allow the last item (current page) to truncate gracefully instead of hard-clipping */
160
+ :where([data-controller="breadcrumb"]) [data-breadcrumb-part="item"]:where(:last-child) {
161
+ @apply shrink min-w-0 overflow-hidden;
162
+ text-overflow: ellipsis;
163
+ }
132
164
 
133
- /* Items hidden by Stimulus controller */
134
- [data-controller="breadcrumb"] [data-breadcrumb-target="item"].hidden,
135
- [data-controller="breadcrumb"] [data-breadcrumb-target="ellipsisSeparator"].hidden {
136
- @apply hidden;
137
- }
165
+ :where([data-controller="breadcrumb"] [data-breadcrumb-part="item"]:last-child) :is([data-breadcrumb-part="page"], [data-breadcrumb-part="link"]) {
166
+ @apply overflow-hidden;
167
+ text-overflow: ellipsis;
168
+ }
138
169
 
139
- /* Ellipsis shown by Stimulus controller */
140
- [data-controller="breadcrumb"] [data-breadcrumb-target="ellipsis"]:not(.hidden) {
141
- @apply inline-flex;
142
- }
170
+ /* Items hidden by Stimulus controller */
171
+ :where([data-controller="breadcrumb"]) [data-breadcrumb-target="item"]:where(.hidden),
172
+ :where([data-controller="breadcrumb"]) [data-breadcrumb-target="ellipsisSeparator"]:where(.hidden) {
173
+ @apply hidden;
174
+ }
143
175
 
144
- /* ===== Mobile Adjustments ===== */
145
- @media (max-width: 640px) {
146
- [data-breadcrumb-part="list"] {
147
- @apply gap-1;
176
+ /* Ellipsis shown by Stimulus controller */
177
+ :where([data-controller="breadcrumb"]) [data-breadcrumb-target="ellipsis"]:where(:not(.hidden)) {
178
+ @apply inline-flex;
148
179
  }
149
180
 
150
- /* Hide middle items on very small screens by default */
151
- [data-component="breadcrumbs"][data-auto-collapse] [data-breadcrumb-part="item"]:not(:first-child):not(:last-child) {
152
- @apply hidden sm:inline-flex;
181
+ /* ===== Mobile Adjustments ===== */
182
+ @media (max-width: 640px) {
183
+ [data-breadcrumb-part="list"] {
184
+ @apply gap-1;
185
+ }
186
+
187
+ /* Hide middle items on very small screens by default */
188
+ :where([data-component="breadcrumbs"][data-auto-collapse]) [data-breadcrumb-part="item"]:where(:not(:first-child):not(:last-child)) {
189
+ @apply hidden sm:inline-flex;
190
+ }
153
191
  }
154
- }
155
192
 
156
- /* ===== Variants ===== */
193
+ /* ===== Separator Variants ===== */
157
194
 
158
- /* Slash separator variant */
159
- [data-component="breadcrumbs"][data-separator="slash"] [data-breadcrumb-part="separator"]::before {
160
- content: "/";
161
- @apply px-1;
162
- }
195
+ /* Slash separator variant */
196
+ :where([data-component="breadcrumbs"][data-separator="slash"]) [data-breadcrumb-part="separator"]::before {
197
+ content: "/";
198
+ @apply px-1;
199
+ }
163
200
 
164
- [data-component="breadcrumbs"][data-separator="slash"] [data-breadcrumb-part="separator"] svg {
165
- @apply hidden;
166
- }
201
+ :where([data-component="breadcrumbs"][data-separator="slash"]) [data-breadcrumb-part="separator"] svg {
202
+ @apply hidden;
203
+ }
167
204
 
168
- /* Dot separator variant */
169
- [data-component="breadcrumbs"][data-separator="dot"] [data-breadcrumb-part="separator"]::before {
170
- content: "•";
171
- @apply px-1;
172
- }
205
+ /* Dot separator variant */
206
+ :where([data-component="breadcrumbs"][data-separator="dot"]) [data-breadcrumb-part="separator"]::before {
207
+ content: "•";
208
+ @apply px-1;
209
+ }
173
210
 
174
- [data-component="breadcrumbs"][data-separator="dot"] [data-breadcrumb-part="separator"] svg {
175
- @apply hidden;
176
- }
211
+ :where([data-component="breadcrumbs"][data-separator="dot"]) [data-breadcrumb-part="separator"] svg {
212
+ @apply hidden;
213
+ }
177
214
 
178
- /* Arrow separator variant */
179
- [data-component="breadcrumbs"][data-separator="arrow"] [data-breadcrumb-part="separator"]::before {
180
- content: "→";
181
- @apply px-1;
182
- }
215
+ /* Arrow separator variant */
216
+ :where([data-component="breadcrumbs"][data-separator="arrow"]) [data-breadcrumb-part="separator"]::before {
217
+ content: "→";
218
+ @apply px-1;
219
+ }
183
220
 
184
- [data-component="breadcrumbs"][data-separator="arrow"] [data-breadcrumb-part="separator"] svg {
185
- @apply hidden;
221
+ :where([data-component="breadcrumbs"][data-separator="arrow"]) [data-breadcrumb-part="separator"] svg {
222
+ @apply hidden;
223
+ }
186
224
  }