maverick-wave 4.28.0 → 5.0.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 (138) hide show
  1. package/.claude/settings.local.json +29 -1
  2. package/.claude/skills/mw-maverick-wave/SKILL.md +39 -24
  3. package/.claude/skills/mw-maverick-wave/examples/angular-services.md +16 -4
  4. package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +13 -4
  5. package/.claude/skills/mw-maverick-wave/references/components.md +42 -5
  6. package/.claude/skills/mw-maverick-wave/references/forms.md +14 -2
  7. package/.claude/skills/mw-maverick-wave/references/javascript.md +17 -4
  8. package/.claude/skills/mw-maverick-wave/references/layout.md +53 -19
  9. package/.claude/skills/mw-maverick-wave/references/theming.md +38 -1
  10. package/.impeccable/config.local.json +5 -0
  11. package/.impeccable/hook.cache.json +1 -0
  12. package/CHANGELOG.md +11 -0
  13. package/CLAUDE.md +5 -5
  14. package/README.md +48 -4
  15. package/index.html +19 -38
  16. package/maverick-wave.min.css +14 -12
  17. package/maverick-wave.min.js +1 -1
  18. package/package.json +2 -2
  19. package/scripts/verify.js +27 -2
  20. package/src/js/main.js +223 -83
  21. package/src/partials/accordions-container.html +6 -10
  22. package/src/partials/alerts-container.html +6 -6
  23. package/src/partials/announcement-container.html +5 -5
  24. package/src/partials/avatars-container.html +85 -17
  25. package/src/partials/badges-container.html +6 -6
  26. package/src/partials/blog-posts-container.html +11 -12
  27. package/src/partials/buttons-container.html +3 -3
  28. package/src/partials/cards-container.html +26 -9
  29. package/src/partials/code-container.html +5 -5
  30. package/src/partials/content-slider-container.html +5 -4
  31. package/src/partials/divider-container.html +7 -7
  32. package/src/partials/dropdown-container.html +2 -2
  33. package/src/partials/empty-state-container.html +5 -5
  34. package/src/partials/footer-container.html +21 -18
  35. package/src/partials/form-elements-container.html +14 -14
  36. package/src/partials/gallery-container.html +16 -8
  37. package/src/partials/get-started-container.html +4 -8
  38. package/src/partials/header-container.html +18 -7
  39. package/src/partials/home-container.html +10 -20
  40. package/src/partials/input-group-container.html +11 -2
  41. package/src/partials/kbd-container.html +1 -1
  42. package/src/partials/login-container.html +5 -1
  43. package/src/partials/media-container.html +2 -2
  44. package/src/partials/modals-container.html +22 -32
  45. package/src/partials/pricing-container.html +9 -9
  46. package/src/partials/skeleton-container.html +6 -6
  47. package/src/partials/stepper-container.html +2 -2
  48. package/src/partials/tables-container.html +12 -12
  49. package/src/partials/tabs-container.html +17 -27
  50. package/src/partials/tags-container.html +23 -17
  51. package/src/partials/techstack-bucket-container.html +13 -0
  52. package/src/partials/tiles-container.html +26 -9
  53. package/src/partials/typography-container.html +7 -6
  54. package/src/partials/utilities-container.html +35 -15
  55. package/src/scss/_layers.scss +10 -0
  56. package/src/scss/abstracts/_index.scss +6 -0
  57. package/src/scss/abstracts/_mixins.scss +61 -153
  58. package/src/scss/abstracts/_variables.scss +123 -319
  59. package/src/scss/base/_base.scss +170 -190
  60. package/src/scss/base/_reset.scss +106 -104
  61. package/src/scss/base/_typography.scss +153 -151
  62. package/src/scss/components/_accordions.scss +97 -97
  63. package/src/scss/components/_alerts.scss +71 -70
  64. package/src/scss/components/_announcement.scss +71 -70
  65. package/src/scss/components/_avatars.scss +130 -142
  66. package/src/scss/components/_badge.scss +131 -132
  67. package/src/scss/components/_blog-post.scss +223 -220
  68. package/src/scss/components/_breadcrumbs.scss +93 -92
  69. package/src/scss/components/_button-bar.scss +102 -110
  70. package/src/scss/components/_buttons.scss +319 -324
  71. package/src/scss/components/_calendar.scss +251 -252
  72. package/src/scss/components/_cards.scss +359 -386
  73. package/src/scss/components/_code.scss +190 -194
  74. package/src/scss/components/_coming-soon.scss +77 -75
  75. package/src/scss/components/_content-slider.scss +90 -89
  76. package/src/scss/components/_divider.scss +55 -53
  77. package/src/scss/components/_dropdown.scss +179 -185
  78. package/src/scss/components/_empty-state.scss +57 -57
  79. package/src/scss/components/_flag.scss +294 -278
  80. package/src/scss/components/_gallery.scss +131 -133
  81. package/src/scss/components/_info.scss +151 -167
  82. package/src/scss/components/_kanban.scss +332 -341
  83. package/src/scss/components/_kbd.scss +40 -38
  84. package/src/scss/components/_lang-switch.scss +89 -97
  85. package/src/scss/components/_links.scss +21 -28
  86. package/src/scss/components/_lists.scss +510 -514
  87. package/src/scss/components/_localhost-indicator.scss +26 -24
  88. package/src/scss/components/_media.scss +22 -20
  89. package/src/scss/components/_meta-info.scss +35 -33
  90. package/src/scss/components/_modals.scss +277 -186
  91. package/src/scss/components/_pagination.scss +81 -87
  92. package/src/scss/components/_panels.scss +99 -97
  93. package/src/scss/components/_pricing.scss +183 -241
  94. package/src/scss/components/_progress.scss +112 -89
  95. package/src/scss/components/_prose.scss +35 -33
  96. package/src/scss/components/_ratings.scss +40 -38
  97. package/src/scss/components/_segmented.scss +82 -84
  98. package/src/scss/components/_skeleton.scss +66 -72
  99. package/src/scss/components/_spinners.scss +155 -159
  100. package/src/scss/components/_stepper.scss +116 -116
  101. package/src/scss/components/_tables.scss +155 -148
  102. package/src/scss/components/_tabs.scss +232 -141
  103. package/src/scss/components/_tags.scss +109 -107
  104. package/src/scss/components/_techstack-bucket.scss +123 -122
  105. package/src/scss/components/_testimonial.scss +72 -70
  106. package/src/scss/components/_theme-toggle.scss +53 -51
  107. package/src/scss/components/_tiles.scss +174 -174
  108. package/src/scss/components/_timelines.scss +282 -280
  109. package/src/scss/components/_toasts.scss +66 -72
  110. package/src/scss/form-elements/_checkbox.scss +117 -132
  111. package/src/scss/form-elements/_form.scss +149 -115
  112. package/src/scss/form-elements/_input-group.scss +99 -103
  113. package/src/scss/form-elements/_input.scss +74 -74
  114. package/src/scss/form-elements/_login.scss +48 -48
  115. package/src/scss/form-elements/_prefilled.scss +53 -51
  116. package/src/scss/form-elements/_radio.scss +95 -107
  117. package/src/scss/form-elements/_select.scss +42 -42
  118. package/src/scss/form-elements/_slider.scss +117 -132
  119. package/src/scss/form-elements/_textarea.scss +68 -54
  120. package/src/scss/form-elements/_toggle.scss +127 -140
  121. package/src/scss/layout/_footer.scss +172 -171
  122. package/src/scss/layout/_grid.scss +268 -302
  123. package/src/scss/layout/_header.scss +426 -427
  124. package/src/scss/layout/_home.scss +28 -27
  125. package/src/scss/layout/_main.scss +275 -238
  126. package/src/scss/layout/_page-header.scss +38 -36
  127. package/src/scss/layout/_section.scss +150 -124
  128. package/src/scss/main.scss +1 -1
  129. package/src/scss/utilities/_accessibility.scss +46 -45
  130. package/src/scss/utilities/_aspect.scss +22 -20
  131. package/src/scss/utilities/_corner.scss +23 -5
  132. package/src/scss/utilities/_display.scss +76 -70
  133. package/src/scss/utilities/_elevation.scss +13 -11
  134. package/src/scss/utilities/_flex.scss +83 -85
  135. package/src/scss/utilities/_reveal.scss +40 -63
  136. package/src/scss/utilities/_spacing.scss +63 -64
  137. package/src/scss/utilities/_text.scss +139 -141
  138. package/src/scss/utilities/_touch-targets.scss +130 -147
@@ -1,398 +1,389 @@
1
1
  @use '../abstracts' as *;
2
2
 
3
- // Pixel-precise shape values - no fractional keys in the spacing map
4
- $_mw-kanban-action-size: 26px;
5
- $_mw-kanban-action-size-touch: 32px;
6
- // Room the ribbon needs in the top right corner of a ticket
7
- $_mw-kanban-ribbon-clearance: 66px;
8
- $_mw-kanban-ribbon-clearance-compact: 58px;
9
-
10
- @keyframes mw-kanban-card-in {
11
- from {
12
- opacity: 0;
13
- transform: translateY(-6px);
3
+ @layer mw.components {
4
+ // Pixel-precise shape values - no fractional keys in the spacing map
5
+ $_mw-kanban-action-size: 26px;
6
+ $_mw-kanban-action-size-touch: 32px;
7
+ // Room the ribbon needs in the top right corner of a ticket
8
+ $_mw-kanban-ribbon-clearance: 66px;
9
+ $_mw-kanban-ribbon-clearance-compact: 58px;
10
+
11
+ @keyframes mw-kanban-card-in {
12
+ from {
13
+ opacity: 0;
14
+ transform: translateY(-6px);
15
+ }
16
+ to {
17
+ opacity: 1;
18
+ transform: translateY(0);
19
+ }
14
20
  }
15
- to {
16
- opacity: 1;
17
- transform: translateY(0);
18
- }
19
- }
20
21
 
21
- // A card that has just been pushed to another lane arrives from the side it was
22
- // pushed from, and takes visibly longer doing it than a new card takes to
23
- // appear. The two are not the same event: a new card shows up exactly where the
24
- // eye already is - the composer it was written in - while a moved one lands in a
25
- // column somewhere else on the board, and has to be found there.
26
- //
27
- // Before this the move had no animation at all: the JS re-parented the card and
28
- // it simply blinked into the other lane.
29
- @keyframes mw-kanban-card-moved-forward {
30
- from {
31
- opacity: 0;
32
- transform: translateX(-18px);
22
+ // A card that has just been pushed to another lane arrives from the side it was
23
+ // pushed from, and takes visibly longer doing it than a new card takes to
24
+ // appear. The two are not the same event: a new card shows up exactly where the
25
+ // eye already is - the composer it was written in - while a moved one lands in a
26
+ // column somewhere else on the board, and has to be found there.
27
+ //
28
+ // Before this the move had no animation at all: the JS re-parented the card and
29
+ // it simply blinked into the other lane.
30
+ @keyframes mw-kanban-card-moved-forward {
31
+ from {
32
+ opacity: 0;
33
+ transform: translateX(-18px);
34
+ }
33
35
  }
34
- }
35
36
 
36
- @keyframes mw-kanban-card-moved-back {
37
- from {
38
- opacity: 0;
39
- transform: translateX(18px);
37
+ @keyframes mw-kanban-card-moved-back {
38
+ from {
39
+ opacity: 0;
40
+ transform: translateX(18px);
41
+ }
40
42
  }
41
- }
42
43
 
43
- // ---------------------------------------------------------------------------
44
- // Board
45
- // ---------------------------------------------------------------------------
46
- .mw-kanban {
47
- // Board surface - drop it with .mw-kanban-plain or override per board.
48
- --mw-kanban-background: color-mix(
49
- in srgb,
50
- var(--mw-gray-color) 40%,
51
- transparent
52
- );
53
- --mw-kanban-lane-border: 1px dashed
54
- color-mix(in srgb, var(--mw-text-muted-color) 40%, transparent);
55
- // Keeps an empty lane from collapsing onto its header - raise it per board
56
- // to give a board that starts out empty some presence
57
- --mw-kanban-column-min-height: 120px;
58
-
59
- display: grid;
60
- grid-auto-flow: column;
61
- grid-auto-columns: minmax(0, 1fr);
62
- gap: spacing('4');
63
- padding: spacing('4');
64
- background: var(--mw-kanban-background);
65
- border-radius: radius('md');
66
-
67
- // Two lanes side by side stay readable, four do not
68
- @include media-down('lg') {
69
- grid-auto-flow: row;
70
- grid-template-columns: repeat(2, minmax(0, 1fr));
44
+ // ---------------------------------------------------------------------------
45
+ // Board
46
+ // ---------------------------------------------------------------------------
47
+ .mw-kanban {
48
+ // Board surface - drop it with .mw-kanban-plain or override per board.
49
+ --mw-kanban-background: color-mix(
50
+ in srgb,
51
+ var(--mw-gray-color) 40%,
52
+ transparent
53
+ );
54
+ --mw-kanban-lane-border: 1px dashed
55
+ color-mix(in srgb, var(--mw-text-muted-color) 40%, transparent);
56
+ // Keeps an empty lane from collapsing onto its header - raise it per board
57
+ // to give a board that starts out empty some presence
58
+ --mw-kanban-column-min-height: 120px;
59
+
60
+ display: grid;
61
+ grid-auto-flow: column;
62
+ grid-auto-columns: minmax(0, 1fr);
63
+ gap: spacing('4');
64
+ padding: spacing('4');
65
+ background: var(--mw-kanban-background);
66
+ border-radius: radius('md');
67
+
68
+ // Two lanes side by side stay readable, four do not
69
+ @include media-down('lg') {
70
+ grid-auto-flow: row;
71
+ grid-template-columns: repeat(2, minmax(0, 1fr));
72
+ }
73
+
74
+ @include media-down('sm') {
75
+ grid-template-columns: minmax(0, 1fr);
76
+ gap: spacing('3');
77
+ padding: spacing('3');
78
+ }
71
79
  }
72
80
 
73
- @include media-down('sm') {
74
- grid-template-columns: minmax(0, 1fr);
75
- gap: spacing('3');
76
- padding: spacing('3');
81
+ // Board without its own surface - for pages that already sit on a panel
82
+ .mw-kanban-plain {
83
+ --mw-kanban-background: transparent;
84
+ padding: spacing('0');
77
85
  }
78
- }
79
-
80
- // Board without its own surface - for pages that already sit on a panel
81
- .mw-kanban-plain {
82
- --mw-kanban-background: transparent;
83
- padding: spacing('0');
84
- }
85
-
86
- // Lane
87
- .mw-kanban-column {
88
- display: flex;
89
- flex-direction: column;
90
- min-width: 0;
91
- padding: spacing('4') spacing('3');
92
- border: var(--mw-kanban-lane-border);
93
- border-radius: radius('sm');
94
- }
95
-
96
- .mw-kanban-column-header {
97
- display: flex;
98
- align-items: center;
99
- gap: spacing('3');
100
- margin-bottom: spacing('4');
101
- }
102
86
 
103
- .mw-kanban-title {
104
- flex: 1;
105
- min-width: 0;
106
- margin: spacing('0');
107
- font-size: font-size('sm');
108
- font-weight: font-weight('bold');
109
- color: var(--mw-text-color);
110
- text-transform: uppercase;
111
- letter-spacing: 0.05em;
112
- white-space: nowrap;
113
- overflow: hidden;
114
- text-overflow: ellipsis;
115
- }
116
-
117
- .mw-kanban-count {
118
- display: inline-flex;
119
- align-items: center;
120
- justify-content: center;
121
- min-width: 22px;
122
- padding: spacing('0') spacing('2');
123
- border-radius: radius('xl');
124
- background: var(--mw-gray-background);
125
- border: 1px solid var(--mw-border);
126
- color: var(--mw-text-muted-color);
127
- font-size: font-size('xs');
128
- font-weight: font-weight('medium');
129
- font-variant-numeric: tabular-nums;
130
- flex-shrink: 0;
131
- }
132
-
133
- // Sits on .mw-btn - this only trims it down to the header's line height
134
- .mw-kanban-add {
135
- padding: spacing('1') spacing('3');
136
- line-height: 1;
137
- flex-shrink: 0;
138
- }
139
-
140
- .mw-kanban-column-body {
141
- display: flex;
142
- flex-direction: column;
143
- gap: spacing('3');
144
- // Fills the lane the grid stretched, so the empty state stays centred in it
145
- flex: 1;
146
- min-height: var(--mw-kanban-column-min-height);
147
- }
148
-
149
- // Ticket - a plain .mw-card, only smaller
150
- .mw-kanban-card {
151
- padding: spacing('4');
152
-
153
- &.mw-kanban-card-in {
154
- animation: mw-kanban-card-in var(--mw-duration-base) var(--mw-ease-out);
87
+ // Lane
88
+ .mw-kanban-column {
89
+ display: flex;
90
+ flex-direction: column;
91
+ min-width: 0;
92
+ padding: spacing('4') spacing('3');
93
+ border: var(--mw-kanban-lane-border);
94
+ border-radius: radius('sm');
155
95
  }
156
96
 
157
- // `slow` rather than `base` - see the note by the keyframes. Retune the whole
158
- // thing through --mw-duration-slow, or give these two rules a duration of
159
- // their own if only the board should change.
160
- &.mw-kanban-card-moved-forward {
161
- animation: mw-kanban-card-moved-forward var(--mw-duration-slow)
162
- var(--mw-ease-out);
97
+ .mw-kanban-column-header {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: spacing('3');
101
+ margin-bottom: spacing('4');
163
102
  }
164
103
 
165
- &.mw-kanban-card-moved-back {
166
- animation: mw-kanban-card-moved-back var(--mw-duration-slow)
167
- var(--mw-ease-out);
104
+ .mw-kanban-title {
105
+ flex: 1;
106
+ min-width: 0;
107
+ margin: spacing('0');
108
+ font-size: font-size('sm');
109
+ font-weight: font-weight('bold');
110
+ color: var(--mw-text-color);
111
+ text-transform: uppercase;
112
+ letter-spacing: 0.05em;
113
+ @include truncate;
168
114
  }
169
- }
170
115
 
171
- // The card ribbon carries the priority, including its color
172
- .mw-kanban-card .mw-card-ribbon {
173
- top: 10px;
174
- right: -34px;
175
- width: 120px;
176
- padding: spacing('0') spacing('3');
177
- font-size: font-size('2xs');
178
- opacity: 1;
179
- }
180
-
181
- // Keeps the first lines clear of the ribbon - only where there is one
182
- .mw-kanban-card:has(> .mw-card-ribbon) .mw-kanban-card-title {
183
- padding-right: $_mw-kanban-ribbon-clearance;
184
- }
185
-
186
- .mw-kanban-card-title {
187
- margin: spacing('0') spacing('0') spacing('2');
188
- font-size: font-size('base');
189
- font-weight: font-weight('medium');
190
- color: var(--mw-text-color);
191
- line-height: 1.35;
192
- }
193
-
194
- // Caps the description so a wordy ticket cannot dwarf the ones next to it
195
- .mw-kanban-card-text {
196
- display: -webkit-box;
197
- -webkit-line-clamp: 4;
198
- line-clamp: 4;
199
- -webkit-box-orient: vertical;
200
- overflow: hidden;
201
- margin: spacing('0');
202
- font-size: font-size('sm');
203
- color: var(--mw-text-muted-color);
204
- line-height: 1.45;
205
- }
206
-
207
- // Footer - ticket key on the left, assignee and the action buttons on the right
208
- .mw-kanban-card-footer {
209
- display: flex;
210
- align-items: center;
211
- flex-wrap: wrap;
212
- gap: spacing('2');
213
- margin-top: spacing('4');
214
- padding-top: spacing('3');
215
- border-top: 1px solid var(--mw-border);
216
- }
217
-
218
- // Monospaced so keys of equal length line up across cards
219
- .mw-kanban-card-id {
220
- font-family: var(--mw-font-family-mono);
221
- font-size: font-size('xs');
222
- color: var(--mw-text-muted-color);
223
- letter-spacing: 0.03em;
224
- flex-shrink: 0;
225
- }
116
+ .mw-kanban-count {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ min-width: 22px;
121
+ padding: spacing('0') spacing('2');
122
+ border-radius: radius('xl');
123
+ background: var(--mw-gray-background);
124
+ border: 1px solid var(--mw-border);
125
+ color: var(--mw-text-muted-color);
126
+ font-size: font-size('xs');
127
+ font-weight: font-weight('medium');
128
+ font-variant-numeric: tabular-nums;
129
+ flex-shrink: 0;
130
+ }
226
131
 
227
- .mw-kanban-card-actions {
228
- display: flex;
229
- align-items: center;
230
- gap: spacing('2');
231
- margin-left: auto;
232
- flex-shrink: 0;
132
+ // Sits on .mw-btn - this only trims it down to the header's line height
133
+ .mw-kanban-add {
134
+ padding: spacing('1') spacing('3');
135
+ line-height: 1;
136
+ flex-shrink: 0;
137
+ }
233
138
 
234
- // The global avatar margin would push the buttons off the card
235
- .mw-avatar {
236
- margin: spacing('0');
139
+ .mw-kanban-column-body {
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: spacing('3');
143
+ // Fills the lane the grid stretched, so the empty state stays centred in it
144
+ flex: 1;
145
+ min-height: var(--mw-kanban-column-min-height);
237
146
  }
238
- }
239
147
 
240
- // Move, edit and delete all share one button shape
241
- .mw-kanban-action {
242
- display: inline-flex;
243
- align-items: center;
244
- justify-content: center;
245
- width: $_mw-kanban-action-size;
246
- height: $_mw-kanban-action-size;
247
- padding: spacing('0');
248
- border: 1px solid var(--mw-border);
249
- border-radius: radius('sm');
250
- background: transparent;
251
- color: var(--mw-text-muted-color);
252
- font-size: font-size('xs');
253
- line-height: 1;
254
- cursor: pointer;
255
- transition: var(--mw-transition);
256
-
257
- &:hover:not(:disabled) {
258
- background: var(--mw-primary-color);
259
- border-color: var(--mw-primary-text-color);
260
- color: var(--mw-primary-accent-text-color);
148
+ // Ticket - a plain .mw-card, only smaller
149
+ .mw-kanban-card {
150
+ padding: spacing('4');
151
+
152
+ &.mw-kanban-card-in {
153
+ animation: mw-kanban-card-in var(--mw-duration-base) var(--mw-ease-out);
154
+ }
155
+
156
+ // `slow` rather than `base` - see the note by the keyframes. Retune the whole
157
+ // thing through --mw-duration-slow, or give these two rules a duration of
158
+ // their own if only the board should change.
159
+ &.mw-kanban-card-moved-forward {
160
+ animation: mw-kanban-card-moved-forward var(--mw-duration-slow)
161
+ var(--mw-ease-out);
162
+ }
163
+
164
+ &.mw-kanban-card-moved-back {
165
+ animation: mw-kanban-card-moved-back var(--mw-duration-slow)
166
+ var(--mw-ease-out);
167
+ }
261
168
  }
262
169
 
263
- @include focus-ring;
170
+ // The card ribbon carries the priority, including its color
171
+ .mw-kanban-card .mw-card-ribbon {
172
+ top: 10px;
173
+ right: -34px;
174
+ width: 120px;
175
+ padding: spacing('0') spacing('3');
176
+ font-size: font-size('2xs');
177
+ opacity: 1;
178
+ }
264
179
 
265
- &:disabled {
266
- opacity: 0.35;
267
- cursor: not-allowed;
180
+ // Keeps the first lines clear of the ribbon - only where there is one
181
+ .mw-kanban-card:has(> .mw-card-ribbon) .mw-kanban-card-title {
182
+ padding-right: $_mw-kanban-ribbon-clearance;
268
183
  }
269
184
 
270
- // Comfortable tap target where there is no mouse
271
- @include media-down('md') {
272
- width: $_mw-kanban-action-size-touch;
273
- height: $_mw-kanban-action-size-touch;
185
+ .mw-kanban-card-title {
186
+ margin: spacing('0') spacing('0') spacing('2');
187
+ font-size: font-size('base');
188
+ font-weight: font-weight('medium');
189
+ color: var(--mw-text-color);
190
+ line-height: 1.35;
274
191
  }
275
- }
276
192
 
277
- .mw-kanban-action-danger:hover:not(:disabled) {
278
- background: var(--mw-danger-color);
279
- border-color: var(--mw-danger-color);
280
- }
193
+ // Caps the description so a wordy ticket cannot dwarf the ones next to it
194
+ .mw-kanban-card-text {
195
+ display: -webkit-box;
196
+ -webkit-line-clamp: 4;
197
+ line-clamp: 4;
198
+ -webkit-box-orient: vertical;
199
+ overflow: hidden;
200
+ margin: spacing('0');
201
+ font-size: font-size('sm');
202
+ color: var(--mw-text-muted-color);
203
+ line-height: 1.45;
204
+ }
281
205
 
282
- // Composer - one per board, moved into whichever lane it is needed in
283
- .mw-kanban-composer {
284
- display: none;
285
- flex-direction: column;
286
- gap: spacing('3');
287
- padding: spacing('4');
288
- background: var(--mw-card-background);
289
- border: 1px dashed var(--mw-primary-text-color);
290
- // Same silhouette as a ticket, so it reads as one being written
291
- @include surface;
292
-
293
- &.mw-active {
206
+ // Footer - ticket key on the left, assignee and the action buttons on the right
207
+ .mw-kanban-card-footer {
294
208
  display: flex;
295
- animation: mw-kanban-card-in var(--mw-duration-fast) var(--mw-ease-out);
209
+ align-items: center;
210
+ flex-wrap: wrap;
211
+ gap: spacing('2');
212
+ margin-top: spacing('4');
213
+ padding-top: spacing('3');
214
+ border-top: 1px solid var(--mw-border);
296
215
  }
297
216
 
298
- // Three lines of description without a scrollbar
299
- .mw-textarea {
300
- min-height: 66px;
217
+ // Monospaced so keys of equal length line up across cards
218
+ .mw-kanban-card-id {
219
+ font-family: var(--mw-font-family-mono);
220
+ font-size: font-size('xs');
221
+ color: var(--mw-text-muted-color);
222
+ letter-spacing: 0.03em;
223
+ flex-shrink: 0;
301
224
  }
302
- }
303
225
 
304
- // Priority and assignee side by side, stacked once the lane gets too narrow
305
- .mw-kanban-composer-row {
306
- display: flex;
307
- flex-wrap: wrap;
308
- gap: spacing('3');
226
+ .mw-kanban-card-actions {
227
+ display: flex;
228
+ align-items: center;
229
+ gap: spacing('2');
230
+ margin-left: auto;
231
+ flex-shrink: 0;
309
232
 
310
- > * {
311
- flex: 1 1 104px;
233
+ // The global avatar margin would push the buttons off the card
234
+ .mw-avatar {
235
+ margin: spacing('0');
236
+ }
312
237
  }
313
- }
314
-
315
- .mw-kanban-composer-actions {
316
- display: flex;
317
- align-items: center;
318
- justify-content: flex-end;
319
- flex-wrap: wrap;
320
- gap: spacing('3');
321
- }
322
-
323
- // The ticket being edited steps aside for the composer that replaces it
324
- .mw-kanban-editing {
325
- display: none;
326
- }
327
-
328
- // Empty lane placeholder
329
- .mw-kanban-empty {
330
- display: flex;
331
- align-items: center;
332
- justify-content: center;
333
- flex: 1;
334
- padding: spacing('5') spacing('3');
335
- color: var(--mw-text-muted-color);
336
- font-size: font-size('sm');
337
- text-align: center;
338
- }
339
-
340
- .mw-kanban-column-body:has(.mw-kanban-card, .mw-kanban-composer.mw-active)
341
- .mw-kanban-empty {
342
- display: none;
343
- }
344
-
345
- // Compact board - for many lanes or a board next to a sidebar
346
- .mw-kanban-compact {
347
- --mw-kanban-column-min-height: 90px;
348
238
 
349
- .mw-kanban-column {
350
- padding: spacing('3');
239
+ // Move, edit and delete all share one button shape
240
+ .mw-kanban-action {
241
+ display: inline-flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ width: $_mw-kanban-action-size-touch;
245
+ height: $_mw-kanban-action-size-touch;
246
+ padding: spacing('0');
247
+ border: 1px solid var(--mw-border);
248
+ border-radius: radius('sm');
249
+ background: transparent;
250
+ color: var(--mw-text-muted-color);
251
+ font-size: font-size('xs');
252
+ line-height: 1;
253
+ cursor: pointer;
254
+ transition: var(--mw-transition);
255
+
256
+ &:hover:not(:disabled) {
257
+ background: var(--mw-primary-color);
258
+ border-color: var(--mw-primary-text-color);
259
+ color: var(--mw-primary-accent-text-color);
260
+ }
261
+
262
+ @include focus-ring;
263
+
264
+ &:disabled {
265
+ opacity: 0.35;
266
+ cursor: not-allowed;
267
+ }
268
+
269
+ // Comfortable tap target where there is no mouse
270
+ @include media-up('md') {
271
+ width: $_mw-kanban-action-size;
272
+ height: $_mw-kanban-action-size;
273
+ }
351
274
  }
352
275
 
353
- .mw-kanban-column-header {
354
- margin-bottom: spacing('3');
276
+ .mw-kanban-action-danger:hover:not(:disabled) {
277
+ background: var(--mw-danger-color);
278
+ border-color: var(--mw-danger-color);
355
279
  }
356
280
 
357
- .mw-kanban-column-body {
358
- gap: spacing('2');
281
+ // Composer - one per board, moved into whichever lane it is needed in
282
+ .mw-kanban-composer {
283
+ display: none;
284
+ flex-direction: column;
285
+ gap: spacing('3');
286
+ padding: spacing('4');
287
+ background: var(--mw-card-background);
288
+ border: 1px dashed var(--mw-primary-text-color);
289
+ // Same silhouette as a ticket, so it reads as one being written
290
+ @include surface;
291
+
292
+ &.mw-active {
293
+ display: flex;
294
+ animation: mw-kanban-card-in var(--mw-duration-fast) var(--mw-ease-out);
295
+ }
296
+
297
+ // Three lines of description without a scrollbar
298
+ .mw-textarea {
299
+ min-height: 66px;
300
+ }
359
301
  }
360
302
 
361
- .mw-kanban-card {
362
- padding: spacing('3');
363
- }
303
+ // Priority and assignee side by side, stacked once the lane gets too narrow
304
+ .mw-kanban-composer-row {
305
+ display: flex;
306
+ flex-wrap: wrap;
307
+ gap: spacing('3');
364
308
 
365
- .mw-kanban-card .mw-card-ribbon {
366
- top: 8px;
367
- right: -36px;
368
- width: 110px;
309
+ > * {
310
+ flex: 1 1 104px;
311
+ }
369
312
  }
370
313
 
371
- .mw-kanban-card:has(> .mw-card-ribbon) .mw-kanban-card-title {
372
- padding-right: $_mw-kanban-ribbon-clearance-compact;
314
+ .mw-kanban-composer-actions {
315
+ display: flex;
316
+ align-items: center;
317
+ justify-content: flex-end;
318
+ flex-wrap: wrap;
319
+ gap: spacing('3');
373
320
  }
374
321
 
375
- .mw-kanban-card-title {
376
- font-size: font-size('sm');
322
+ // The ticket being edited steps aside for the composer that replaces it
323
+ .mw-kanban-editing {
324
+ display: none;
377
325
  }
378
326
 
379
- // Tighter than the four lines a regular board allows
380
- .mw-kanban-card-text {
381
- -webkit-line-clamp: 2;
382
- line-clamp: 2;
327
+ // Empty lane placeholder
328
+ .mw-kanban-empty {
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ flex: 1;
333
+ padding: spacing('5') spacing('3');
334
+ color: var(--mw-text-muted-color);
335
+ font-size: font-size('sm');
336
+ text-align: center;
383
337
  }
384
338
 
385
- .mw-kanban-card-footer {
386
- margin-top: spacing('3');
387
- padding-top: spacing('2');
339
+ .mw-kanban-column-body:has(.mw-kanban-card, .mw-kanban-composer.mw-active)
340
+ .mw-kanban-empty {
341
+ display: none;
388
342
  }
389
- }
390
343
 
391
- @media (prefers-reduced-motion: reduce) {
392
- .mw-kanban-card.mw-kanban-card-in,
393
- .mw-kanban-card.mw-kanban-card-moved-forward,
394
- .mw-kanban-card.mw-kanban-card-moved-back,
395
- .mw-kanban-composer.mw-active {
396
- animation: none;
344
+ // Compact board - for many lanes or a board next to a sidebar
345
+ .mw-kanban-compact {
346
+ --mw-kanban-column-min-height: 90px;
347
+
348
+ .mw-kanban-column {
349
+ padding: spacing('3');
350
+ }
351
+
352
+ .mw-kanban-column-header {
353
+ margin-bottom: spacing('3');
354
+ }
355
+
356
+ .mw-kanban-column-body {
357
+ gap: spacing('2');
358
+ }
359
+
360
+ .mw-kanban-card {
361
+ padding: spacing('3');
362
+ }
363
+
364
+ .mw-kanban-card .mw-card-ribbon {
365
+ top: 8px;
366
+ right: -36px;
367
+ width: 110px;
368
+ }
369
+
370
+ .mw-kanban-card:has(> .mw-card-ribbon) .mw-kanban-card-title {
371
+ padding-right: $_mw-kanban-ribbon-clearance-compact;
372
+ }
373
+
374
+ .mw-kanban-card-title {
375
+ font-size: font-size('sm');
376
+ }
377
+
378
+ // Tighter than the four lines a regular board allows
379
+ .mw-kanban-card-text {
380
+ -webkit-line-clamp: 2;
381
+ line-clamp: 2;
382
+ }
383
+
384
+ .mw-kanban-card-footer {
385
+ margin-top: spacing('3');
386
+ padding-top: spacing('2');
387
+ }
397
388
  }
398
389
  }