maverick-wave 4.9.4 → 4.11.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 (117) hide show
  1. package/.claude/skills/maverick-wave/SKILL.md +112 -12
  2. package/.claude/skills/maverick-wave/references/components.md +152 -4
  3. package/.claude/skills/maverick-wave/references/forms.md +26 -0
  4. package/.claude/skills/maverick-wave/references/javascript.md +34 -27
  5. package/.claude/skills/maverick-wave/references/layout.md +38 -3
  6. package/.claude/skills/maverick-wave/references/theming.md +33 -7
  7. package/CHANGELOG.md +6 -0
  8. package/README.md +79 -4
  9. package/index.html +16 -0
  10. package/maverick-wave.min.css +3 -3
  11. package/maverick-wave.min.js +1 -1
  12. package/package.json +2 -2
  13. package/src/js/main.js +71 -1
  14. package/src/partials/accordions-container.html +5 -0
  15. package/src/partials/alerts-container.html +5 -0
  16. package/src/partials/avatars-container.html +5 -0
  17. package/src/partials/badges-container.html +114 -0
  18. package/src/partials/blog-posts-container.html +5 -0
  19. package/src/partials/breadcrumbs-container.html +1 -1
  20. package/src/partials/button-bar-container.html +5 -0
  21. package/src/partials/buttons-container.html +84 -0
  22. package/src/partials/calendar-container.html +1 -1
  23. package/src/partials/cards-container.html +5 -0
  24. package/src/partials/code-container.html +6 -0
  25. package/src/partials/colors-container.html +1 -1
  26. package/src/partials/coming-soon-container.html +4 -0
  27. package/src/partials/divider-container.html +4 -0
  28. package/src/partials/dropdown-container.html +119 -0
  29. package/src/partials/empty-state-container.html +5 -0
  30. package/src/partials/form-container.html +5 -0
  31. package/src/partials/form-elements-container.html +5 -0
  32. package/src/partials/form-field-container.html +5 -0
  33. package/src/partials/gallery-container.html +5 -0
  34. package/src/partials/get-started-container.html +16 -4
  35. package/src/partials/header-container.html +6 -2
  36. package/src/partials/header-utilities-container.html +8 -2
  37. package/src/partials/html-lists-container.html +5 -0
  38. package/src/partials/info-container.html +4 -0
  39. package/src/partials/input-group-container.html +5 -0
  40. package/src/partials/item-lists-container.html +5 -0
  41. package/src/partials/kanban-container.html +1 -1
  42. package/src/partials/kbd-container.html +63 -0
  43. package/src/partials/meta-info-container.html +5 -0
  44. package/src/partials/pagination-container.html +1 -1
  45. package/src/partials/panels-container.html +4 -0
  46. package/src/partials/progress-container.html +4 -0
  47. package/src/partials/ratings-container.html +1 -1
  48. package/src/partials/skeleton-container.html +5 -0
  49. package/src/partials/spinners-container.html +5 -1
  50. package/src/partials/stepper-container.html +4 -0
  51. package/src/partials/tables-container.html +7 -0
  52. package/src/partials/tabs-container.html +35 -13
  53. package/src/partials/tags-container.html +5 -0
  54. package/src/partials/tiles-container.html +1 -1
  55. package/src/partials/typography-container.html +5 -0
  56. package/src/partials/utilities-container.html +153 -0
  57. package/src/scss/abstracts/_mixins.scss +176 -0
  58. package/src/scss/abstracts/_variables.scss +193 -5
  59. package/src/scss/base/_base.scss +103 -2
  60. package/src/scss/base/_reset.scss +43 -1
  61. package/src/scss/base/_typography.scss +47 -0
  62. package/src/scss/components/_accordions.scss +11 -1
  63. package/src/scss/components/_alerts.scss +3 -3
  64. package/src/scss/components/_avatars.scss +27 -6
  65. package/src/scss/components/_badge.scss +157 -0
  66. package/src/scss/components/_blog-post.scss +10 -7
  67. package/src/scss/components/_breadcrumbs.scss +11 -2
  68. package/src/scss/components/_button-bar.scss +9 -2
  69. package/src/scss/components/_buttons.scss +133 -17
  70. package/src/scss/components/_calendar.scss +13 -2
  71. package/src/scss/components/_cards.scss +12 -5
  72. package/src/scss/components/_code.scss +15 -0
  73. package/src/scss/components/_coming-soon.scss +5 -2
  74. package/src/scss/components/_content-slider.scss +1 -1
  75. package/src/scss/components/_dropdown.scss +219 -0
  76. package/src/scss/components/_gallery.scss +17 -8
  77. package/src/scss/components/_index.scss +3 -0
  78. package/src/scss/components/_info.scss +14 -8
  79. package/src/scss/components/_kanban.scss +40 -6
  80. package/src/scss/components/_kbd.scss +43 -0
  81. package/src/scss/components/_lists.scss +26 -23
  82. package/src/scss/components/_modals.scss +71 -6
  83. package/src/scss/components/_pagination.scss +4 -3
  84. package/src/scss/components/_panels.scss +13 -14
  85. package/src/scss/components/_progress.scss +1 -1
  86. package/src/scss/components/_segmented.scss +8 -4
  87. package/src/scss/components/_stepper.scss +3 -3
  88. package/src/scss/components/_tables.scss +5 -8
  89. package/src/scss/components/_tabs.scss +22 -3
  90. package/src/scss/components/_tags.scss +1 -2
  91. package/src/scss/components/_techstack-bucket.scss +8 -5
  92. package/src/scss/components/_theme-toggle.scss +16 -8
  93. package/src/scss/components/_tiles.scss +14 -4
  94. package/src/scss/components/_timelines.scss +9 -6
  95. package/src/scss/components/_toasts.scss +1 -1
  96. package/src/scss/form-elements/_checkbox.scss +2 -1
  97. package/src/scss/form-elements/_form.scss +2 -2
  98. package/src/scss/form-elements/_input-group.scss +30 -1
  99. package/src/scss/form-elements/_input.scss +10 -10
  100. package/src/scss/form-elements/_login.scss +1 -1
  101. package/src/scss/form-elements/_radio.scss +4 -3
  102. package/src/scss/form-elements/_select.scss +9 -10
  103. package/src/scss/form-elements/_slider.scss +11 -11
  104. package/src/scss/form-elements/_textarea.scss +5 -8
  105. package/src/scss/form-elements/_toggle.scss +6 -5
  106. package/src/scss/layout/_footer.scss +14 -7
  107. package/src/scss/layout/_header.scss +18 -17
  108. package/src/scss/layout/_home.scss +1 -1
  109. package/src/scss/layout/_main.scss +6 -6
  110. package/src/scss/layout/_section.scss +7 -4
  111. package/src/scss/utilities/_accessibility.scss +38 -0
  112. package/src/scss/utilities/_aspect.scss +27 -0
  113. package/src/scss/utilities/_display.scss +52 -0
  114. package/src/scss/utilities/_elevation.scss +16 -0
  115. package/src/scss/utilities/_index.scss +2 -0
  116. package/src/scss/utilities/_text.scss +63 -0
  117. package/src/scss/utilities/_touch-targets.scss +63 -0
@@ -6,11 +6,15 @@
6
6
  align-items: center;
7
7
  justify-content: center;
8
8
  gap: spacing('3');
9
- padding: spacing('3') spacing('4');
9
+ // Block padding sits a step under the inline one on purpose: the height comes
10
+ // from --mw-control-height, and padding above that value would override it
11
+ // and put this button out of line with the field next to it.
12
+ padding: spacing('2') spacing('4');
10
13
  border: 1px solid transparent;
11
14
  border-radius: radius('sm');
12
15
  font-weight: font-weight('medium');
13
16
  line-height: 1.2;
17
+ min-height: var(--mw-control-height);
14
18
  text-decoration: none;
15
19
  cursor: pointer;
16
20
  transition: var(--mw-transition);
@@ -18,19 +22,24 @@
18
22
  font-family: inherit;
19
23
  font-size: inherit;
20
24
 
21
- &:focus-visible {
22
- outline: 2px solid var(--mw-primary-text-color);
23
- outline-offset: 2px;
25
+ @include focus-ring;
26
+ @include tappable;
27
+
28
+ &:active:not(:disabled) {
29
+ transform: translateY(1px);
30
+ transition-duration: var(--mw-duration-instant);
24
31
  }
25
32
 
26
33
  &:disabled {
27
34
  cursor: not-allowed;
28
35
  opacity: 0.6;
36
+ transform: none;
29
37
  }
30
38
  }
31
39
 
32
40
  // Color variants
33
41
  .mw-btn-primary {
42
+ @include btn-solid;
34
43
  background: var(--mw-primary-color);
35
44
  color: var(--mw-primary-accent-text-color);
36
45
  &:hover:not(:disabled) {
@@ -43,6 +52,7 @@
43
52
  }
44
53
  }
45
54
  .mw-btn-secondary {
55
+ @include btn-solid;
46
56
  background: var(--mw-secondary-color);
47
57
  color: var(--mw-secondary-accent-text-color);
48
58
  &:hover:not(:disabled) {
@@ -56,6 +66,7 @@
56
66
  }
57
67
  // Semantic variants for destructive / confirming actions
58
68
  .mw-btn-danger {
69
+ @include btn-solid;
59
70
  background: var(--mw-danger-color);
60
71
  color: var(--mw-danger-accent-text-color);
61
72
  &:hover:not(:disabled) {
@@ -68,6 +79,7 @@
68
79
  }
69
80
  }
70
81
  .mw-btn-success {
82
+ @include btn-solid;
71
83
  background: var(--mw-success-color);
72
84
  color: var(--mw-success-accent-text-color);
73
85
  &:hover:not(:disabled) {
@@ -85,7 +97,12 @@
85
97
  color: var(--mw-primary-text-color);
86
98
  &:hover:not(:disabled) {
87
99
  background: var(--mw-primary-color);
100
+ border-color: var(--mw-primary-color);
88
101
  color: var(--mw-primary-accent-text-color);
102
+ box-shadow: var(--mw-elevation-1);
103
+ }
104
+ &:active:not(:disabled) {
105
+ box-shadow: inset 0 2px 5px color-mix(in srgb, #000 25%, transparent);
89
106
  }
90
107
  &.mw-active:not(:disabled),
91
108
  &.active:not(:disabled) {
@@ -93,26 +110,111 @@
93
110
  color: var(--mw-primary-accent-text-color);
94
111
  }
95
112
  }
96
- .mw-btn-link {
113
+
114
+ .mw-btn-link,
115
+ .mw-btn-link-muted {
97
116
  background: transparent;
98
117
  border: none;
99
118
  padding: 0;
100
- color: var(--mw-primary-text-color);
119
+ text-underline-offset: 0.22em;
120
+ text-decoration-thickness: 1.5px;
121
+
101
122
  &:hover:not(:disabled) {
102
123
  text-decoration: underline;
103
124
  }
104
125
  }
126
+
127
+ .mw-btn-link {
128
+ color: var(--mw-primary-text-color);
129
+ }
130
+
105
131
  .mw-btn-link-muted {
106
- background: transparent;
107
- border: none;
108
- padding: 0;
109
132
  color: var(--mw-text-muted-color);
110
133
  &:hover:not(:disabled) {
111
134
  color: var(--mw-primary-text-color);
112
- text-decoration: underline;
113
135
  }
114
136
  }
115
137
 
138
+ // The third emphasis level, and the one that was missing. Primary fills, outline
139
+ // draws a line, ghost does neither until you point at it - which is what a
140
+ // toolbar, a card's own actions or a row of icon buttons wants, where five
141
+ // outlined boxes would fight the content they sit on.
142
+ //
143
+ // Not the same as `mw-btn-plain`: this one keeps the button's shape, padding and
144
+ // hit area, it just does not paint them at rest.
145
+ .mw-btn-ghost {
146
+ background: transparent;
147
+ border-color: transparent;
148
+ color: var(--mw-text-color);
149
+
150
+ &:hover:not(:disabled) {
151
+ background: var(--mw-primary-background);
152
+ color: var(--mw-primary-text-color);
153
+ }
154
+
155
+ &:active:not(:disabled) {
156
+ background: var(--mw-primary-background-hover);
157
+ }
158
+
159
+ &.mw-active:not(:disabled),
160
+ &.active:not(:disabled) {
161
+ background: var(--mw-primary-background);
162
+ color: var(--mw-primary-text-color);
163
+ }
164
+ }
165
+
166
+ .mw-btn-ghost-danger {
167
+ color: var(--mw-danger-text-color);
168
+
169
+ &:hover:not(:disabled) {
170
+ background: var(--mw-danger-info-background);
171
+ color: var(--mw-danger-text-color);
172
+ }
173
+
174
+ &:active:not(:disabled) {
175
+ background: var(--mw-danger-info-background-hover);
176
+ }
177
+ }
178
+
179
+ // Square, for a button whose whole label is one icon. Without it the horizontal
180
+ // padding is the only thing setting the width, so an icon button ends up wider
181
+ // than it is tall and a row of them next to a text button lines up on nothing.
182
+ // The width follows the size modifier it is combined with.
183
+ .mw-btn-icon {
184
+ --mw-btn-icon-size: 2.4rem;
185
+
186
+ width: var(--mw-btn-icon-size);
187
+ height: var(--mw-btn-icon-size);
188
+ min-width: 0;
189
+ padding: 0;
190
+ flex-shrink: 0;
191
+
192
+ &.mw-btn-sm {
193
+ --mw-btn-icon-size: 2rem;
194
+ }
195
+
196
+ &.mw-btn-lg {
197
+ --mw-btn-icon-size: 2.9rem;
198
+ }
199
+
200
+ // The floor a thumb needs, over the visual size above
201
+ @media (pointer: coarse), (max-width: #{breakpoint('md')}) {
202
+ --mw-btn-icon-size: 2.75rem;
203
+
204
+ &.mw-btn-sm {
205
+ --mw-btn-icon-size: 2.5rem;
206
+ }
207
+ }
208
+ }
209
+
210
+ // Full width. Mostly a phone thing - a sign-in button, the one action on a
211
+ // sheet - but also right for a narrow sidebar. `flex` rather than `block`, so
212
+ // the icon-and-label centering the base class sets up still holds.
213
+ .mw-btn-block {
214
+ display: flex;
215
+ width: 100%;
216
+ }
217
+
116
218
  // A button that brings nothing but its operability - settings tabs, calendar
117
219
  // days, the time in a request row, a modal backdrop. Everywhere a <button> is
118
220
  // the right element and a button is the wrong look.
@@ -132,10 +234,7 @@
132
234
  text-align: inherit;
133
235
  cursor: pointer;
134
236
 
135
- &:focus-visible {
136
- outline: 2px solid var(--mw-primary-text-color);
137
- outline-offset: 2px;
138
- }
237
+ @include focus-ring;
139
238
 
140
239
  &:disabled {
141
240
  cursor: not-allowed;
@@ -144,23 +243,28 @@
144
243
  }
145
244
 
146
245
  // Size modifiers
246
+ // Buttons run one step above the fields on font size: a button carries a label,
247
+ // a field carries what the user typed, and the label may be the louder of the
248
+ // two. The heights still match, which is the part that shows.
147
249
  .mw-btn-sm {
148
250
  padding: spacing('1') spacing('3');
149
251
  font-size: font-size('sm');
252
+ min-height: var(--mw-control-height-sm);
150
253
  }
151
254
  .mw-btn-lg {
152
255
  padding: spacing('3') spacing('5');
153
256
  font-size: font-size('md');
257
+ min-height: var(--mw-control-height-lg);
154
258
  }
155
259
 
156
260
  // In a row of buttons a two-letter label like "Ja" ends up half the width
157
- // of its neighbour, which reads as an accident
261
+ // of its neighbour, which reads as an accident.
158
262
  .mw-modal-footer,
159
263
  .mw-form-actions,
160
264
  .mw-panel-footer,
161
265
  .mw-button-bar,
162
266
  .mw-kanban-composer-actions {
163
- .mw-btn {
267
+ .mw-btn:not(.mw-btn-icon) {
164
268
  min-width: 80px;
165
269
  }
166
270
  }
@@ -205,7 +309,7 @@
205
309
  height: 18px;
206
310
  border-radius: radius('full');
207
311
  background: var(--mw-gray-color);
208
- color: #efefef;
312
+ color: $fixed-surface-text;
209
313
  border: none;
210
314
  padding: 0;
211
315
  line-height: 1;
@@ -229,10 +333,22 @@
229
333
  opacity: 0.4;
230
334
  }
231
335
 
336
+ @include focus-ring(1px);
337
+ @include tappable;
338
+
232
339
  &:hover:not(:disabled) {
233
340
  background: var(--mw-primary-color);
234
341
  color: var(--mw-primary-accent-text-color);
235
342
  }
343
+
344
+ // Springs back rather than easing back - an 18px circle is small enough that
345
+ // a plain return reads as nothing having happened
346
+ &:active:not(:disabled) {
347
+ transform: scale(0.88);
348
+ transition:
349
+ transform var(--mw-duration-instant) var(--mw-ease-spring),
350
+ var(--mw-transition-fast);
351
+ }
236
352
  }
237
353
 
238
354
  .mw-btn-mini-primary {
@@ -25,7 +25,7 @@ $_mw-calendar-dot-tones: (
25
25
  background: var(--mw-card-background);
26
26
  border: 1px solid var(--mw-border);
27
27
  border-radius: radius('sm') radius('xl') radius('sm') radius('xl');
28
- box-shadow: 0 5px 10px var(--mw-shadow);
28
+ box-shadow: var(--mw-elevation-2);
29
29
  }
30
30
 
31
31
  // Calendar without its own surface - for one that already sits on a card
@@ -107,6 +107,13 @@ $_mw-calendar-dot-tones: (
107
107
  line-height: 1;
108
108
  cursor: pointer;
109
109
  transition: var(--mw-transition);
110
+ -webkit-tap-highlight-color: transparent;
111
+ touch-action: manipulation;
112
+ }
113
+
114
+ .mw-calendar-day:active:not(:disabled) {
115
+ transform: scale(0.92);
116
+ transition-duration: var(--mw-duration-instant);
110
117
  }
111
118
 
112
119
  .mw-calendar-day.mw-calendar-weekend {
@@ -127,7 +134,7 @@ $_mw-calendar-dot-tones: (
127
134
 
128
135
  .mw-calendar-day:focus-visible {
129
136
  outline: none;
130
- box-shadow: 0 0 0 2px var(--mw-primary-background);
137
+ @include focus-halo;
131
138
  border-color: var(--mw-primary-text-color);
132
139
  }
133
140
 
@@ -208,6 +215,10 @@ $_mw-calendar-dot-tones: (
208
215
 
209
216
  .mw-calendar-nav {
210
217
  padding: spacing('1') spacing('3');
218
+ // The nav button is a `.mw-btn` and takes the control scale's minimum
219
+ // height with it, which is what stopped the compact calendar from being
220
+ // compact. A date picker header is chrome, not a form control.
221
+ min-height: var(--mw-control-height-sm);
211
222
  }
212
223
 
213
224
  .mw-calendar-weekday {
@@ -25,7 +25,7 @@ $_mw-card-addon-colors: (
25
25
  border-radius: radius('full');
26
26
  background: var(--mw-card-addon-color, var(--mw-primary-color));
27
27
  color: var(--mw-card-addon-text-color, var(--mw-primary-accent-text-color));
28
- box-shadow: 0 4px 8px var(--mw-shadow);
28
+ box-shadow: var(--mw-elevation-2);
29
29
  transition: var(--mw-transition);
30
30
  }
31
31
 
@@ -48,7 +48,7 @@ $_mw-card-addon-colors: (
48
48
  flex-direction: column;
49
49
  background: var(--mw-card-background);
50
50
  border-radius: radius('sm') radius('xl') radius('sm') radius('xl');
51
- box-shadow: 0 5px 10px var(--mw-shadow);
51
+ box-shadow: var(--mw-elevation-2);
52
52
  border: 1px solid var(--mw-border);
53
53
  transition: var(--mw-transition);
54
54
  position: relative;
@@ -77,11 +77,18 @@ $_mw-card-addon-colors: (
77
77
  }
78
78
 
79
79
  &:hover {
80
- transform: translateY(-2px);
81
- box-shadow: 0 7px 15px var(--mw-shadow);
80
+ box-shadow: var(--mw-elevation-3);
82
81
  border-color: var(--mw-border-accent);
83
82
  }
84
83
 
84
+ // The lift moves the box, so it is guarded: on touch `:hover` latches after a
85
+ // tap and the card stays raised, visibly out of line with the row it sits in.
86
+ // The colour and shadow below are not - a latched border reads as "the one you
87
+ // last touched", which is no worse than nothing.
88
+ @include hover {
89
+ transform: translateY(-2px);
90
+ }
91
+
85
92
  // Padded surface for arbitrary content - stays a block container so inline
86
93
  // children keep flowing instead of turning into flex items
87
94
  &-simple {
@@ -138,7 +145,7 @@ $_mw-card-addon-colors: (
138
145
  width: 100%;
139
146
  height: 100%;
140
147
  object-fit: cover;
141
- transition: var(--mw-transition);
148
+ transition: transform var(--mw-duration-zoom) var(--mw-ease-out);
142
149
  }
143
150
 
144
151
  &:hover img {
@@ -14,6 +14,21 @@ code {
14
14
  word-break: break-word;
15
15
  }
16
16
 
17
+ // Inline code - a token in a sentence, a class name in a table cell - gets a
18
+ // surface of its own so it reads as a quoted thing rather than as prose that
19
+ // changed font halfway through. Only outside a `pre`: the block already paints
20
+ // one, and a second box inside it is just a box inside a box.
21
+ :not(pre) > code {
22
+ padding: 0.15em 0.4em;
23
+ border-radius: radius('xs');
24
+ background: var(--mw-surface-muted);
25
+ color: var(--mw-text-color);
26
+ // Mono faces run visibly larger than the body face at the same nominal size.
27
+ // `em` rather than a step off the scale, so it follows whatever it is set in -
28
+ // a paragraph, a table cell, a heading.
29
+ font-size: 0.88em;
30
+ }
31
+
17
32
  pre {
18
33
  color: var(--mw-text-color);
19
34
  padding: spacing('4');
@@ -47,11 +47,14 @@
47
47
  );
48
48
  opacity: 0;
49
49
  z-index: z-index('lift');
50
- transition: opacity 0.9s ease;
50
+ transition: opacity var(--mw-duration-slower) var(--mw-ease-out);
51
51
  }
52
52
 
53
- &:hover {
53
+ @include hover {
54
54
  transform: translateY(-3px);
55
+ }
56
+
57
+ &:hover {
55
58
  border-style: solid;
56
59
 
57
60
  .mw-coming-soon-icon {
@@ -58,7 +58,7 @@
58
58
  width: 100%;
59
59
  height: 100%;
60
60
  transform: translateX(100%);
61
- transition: transform 0.9s cubic-bezier(0.2, 0, 0.7, 1);
61
+ transition: transform var(--mw-duration-slower) var(--mw-ease-in-out);
62
62
  z-index: z-index('card');
63
63
  pointer-events: auto;
64
64
 
@@ -0,0 +1,219 @@
1
+ @use '../abstracts' as *;
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Dropdown
5
+ //
6
+ // Built on <details>/<summary> rather than a div and a click handler. That one
7
+ // choice buys the whole keyboard contract for free: the trigger is focusable,
8
+ // Enter and Space toggle it, the open state lives in the DOM where a screen
9
+ // reader can see it, and the menu still opens with JavaScript switched off. The
10
+ // script that ships with the framework only adds what markup cannot express -
11
+ // closing on Escape and on a click somewhere else.
12
+ //
13
+ // The menu is absolutely positioned, so an ancestor with `overflow: hidden` -
14
+ // a card with a ribbon, a scroll container - will clip it. That is the same
15
+ // trade the tooltip makes, and for the same reason: the alternative is a fixed
16
+ // element, which breaks the moment any ancestor has a transform.
17
+ // ---------------------------------------------------------------------------
18
+ .mw-dropdown {
19
+ position: relative;
20
+ display: inline-block;
21
+
22
+ // Safari still draws its own triangle through the shadow DOM
23
+ summary::-webkit-details-marker {
24
+ display: none;
25
+ }
26
+
27
+ > summary {
28
+ list-style: none;
29
+ cursor: pointer;
30
+
31
+ &::marker {
32
+ content: '';
33
+ }
34
+ }
35
+
36
+ // The chevron follows the open state, so the trigger says which way it goes
37
+ // without a second class to toggle
38
+ .mw-dropdown-caret {
39
+ display: inline-flex;
40
+ transition: transform var(--mw-duration-fast) var(--mw-ease-out);
41
+ }
42
+
43
+ &[open] .mw-dropdown-caret {
44
+ transform: rotate(180deg);
45
+ }
46
+ }
47
+
48
+ .mw-dropdown-menu {
49
+ position: absolute;
50
+ top: calc(100% + #{spacing('2')});
51
+ left: 0;
52
+ z-index: z-index('menu');
53
+ min-width: 200px;
54
+ // Never wider than the screen it opens on, whatever the longest item says
55
+ max-width: min(320px, calc(100vw - 2 * var(--mw-container-gutter)));
56
+ padding: spacing('2');
57
+ background: var(--mw-card-background);
58
+ border: 1px solid var(--mw-border);
59
+ border-radius: radius('md');
60
+ box-shadow: var(--mw-elevation-4);
61
+ // A long menu scrolls rather than running off the bottom of the page
62
+ max-height: min(60dvh, 420px);
63
+ overflow-y: auto;
64
+ overscroll-behavior: contain;
65
+ @include scrollbar;
66
+
67
+ // Opens downward and settles, rather than appearing. `@starting-style` gives
68
+ // the animation somewhere to start from without a class having to be added
69
+ // by script.
70
+ animation: mw-dropdown-in var(--mw-duration-fast) var(--mw-ease-out);
71
+ }
72
+
73
+ @keyframes mw-dropdown-in {
74
+ from {
75
+ opacity: 0;
76
+ transform: translateY(-6px);
77
+ }
78
+ }
79
+
80
+ // Anchor the menu to the trigger's right edge - what a menu near the right of
81
+ // the screen wants, and what an action menu at the end of a row always wants
82
+ .mw-dropdown-end .mw-dropdown-menu {
83
+ left: auto;
84
+ right: 0;
85
+ }
86
+
87
+ // Open upward, for a trigger that sits low on the page
88
+ .mw-dropdown-up .mw-dropdown-menu {
89
+ top: auto;
90
+ bottom: calc(100% + #{spacing('2')});
91
+
92
+ animation-name: mw-dropdown-in-up;
93
+ }
94
+
95
+ @keyframes mw-dropdown-in-up {
96
+ from {
97
+ opacity: 0;
98
+ transform: translateY(6px);
99
+ }
100
+ }
101
+
102
+ .mw-dropdown-item {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: spacing('3');
106
+ width: 100%;
107
+ padding: spacing('3') spacing('3');
108
+ border: 0;
109
+ border-radius: radius('sm');
110
+ background: none;
111
+ color: var(--mw-text-color);
112
+ font: inherit;
113
+ font-size: font-size('sm');
114
+ text-align: left;
115
+ text-decoration: none;
116
+ cursor: pointer;
117
+ transition: var(--mw-transition-fast);
118
+ @include focus-ring-inset;
119
+ @include tappable;
120
+
121
+ &:hover:not(:disabled) {
122
+ background: var(--mw-primary-background);
123
+ }
124
+
125
+ &:active:not(:disabled) {
126
+ background: var(--mw-primary-background-hover);
127
+ }
128
+
129
+ &:disabled,
130
+ &[aria-disabled='true'] {
131
+ opacity: 0.5;
132
+ cursor: not-allowed;
133
+ pointer-events: none;
134
+ }
135
+
136
+ // The current choice, for a menu that picks rather than acts
137
+ &.mw-active,
138
+ &[aria-checked='true'] {
139
+ background: var(--mw-primary-background);
140
+ color: var(--mw-primary-text-color);
141
+ font-weight: font-weight('medium');
142
+ }
143
+
144
+ // Icons keep one column so the labels line up whether or not every item has
145
+ // one
146
+ i,
147
+ fa-icon,
148
+ svg,
149
+ [class*='fa-'] {
150
+ width: 1.1em;
151
+ flex-shrink: 0;
152
+ text-align: center;
153
+ color: var(--mw-text-muted-color);
154
+ }
155
+ }
156
+
157
+ // Destructive actions belong at the bottom, behind a divider, in the colour
158
+ // that says so
159
+ .mw-dropdown-item-danger {
160
+ color: var(--mw-danger-text-color);
161
+
162
+ i,
163
+ fa-icon,
164
+ svg,
165
+ [class*='fa-'] {
166
+ color: inherit;
167
+ }
168
+
169
+ &:hover:not(:disabled) {
170
+ background: var(--mw-danger-info-background);
171
+ }
172
+
173
+ &:active:not(:disabled) {
174
+ background: var(--mw-danger-info-background-hover);
175
+ }
176
+ }
177
+
178
+ .mw-dropdown-divider {
179
+ height: 1px;
180
+ margin: spacing('2') spacing('1');
181
+ background: var(--mw-border);
182
+ border: 0;
183
+ }
184
+
185
+ // A label over a group of items - "Sort by", "Export as"
186
+ .mw-dropdown-label {
187
+ padding: spacing('2') spacing('3') spacing('1');
188
+ font-size: font-size('2xs');
189
+ font-weight: font-weight('bold');
190
+ letter-spacing: 0.06em;
191
+ text-transform: uppercase;
192
+ color: var(--mw-text-muted-color);
193
+ }
194
+
195
+ // The clipping escape hatch, for the framework's own containers that hide their
196
+ // overflow. See the note at the top of this file: an absolutely positioned menu
197
+ // is cut off by any ancestor that clips, and a card with a ribbon or a scrolling
198
+ // table is exactly where an actions menu ends up.
199
+ .mw-card:has(.mw-dropdown[open]),
200
+ .mw-card-body:has(.mw-dropdown[open]),
201
+ .mw-table-responsive:has(.mw-dropdown[open]),
202
+ .mw-tile:has(.mw-dropdown[open]),
203
+ .mw-modal-body:has(.mw-dropdown[open]) {
204
+ overflow: visible;
205
+ }
206
+
207
+ // A menu opened by a thumb needs rows a thumb can hit, and the room to show
208
+ // them - so it takes the trigger's full width rather than a 200px column
209
+ // floating beside it.
210
+ @media (pointer: coarse), (max-width: #{breakpoint('sm')}) {
211
+ .mw-dropdown-menu {
212
+ min-width: max(100%, 240px);
213
+ }
214
+
215
+ .mw-dropdown-item {
216
+ min-height: 2.75rem;
217
+ font-size: font-size('base');
218
+ }
219
+ }
@@ -13,7 +13,7 @@
13
13
 
14
14
  &-track {
15
15
  display: flex;
16
- transition: transform 0.3s ease;
16
+ transition: transform var(--mw-duration-zoom) var(--mw-ease-in-out);
17
17
 
18
18
  @media (prefers-reduced-motion: reduce) {
19
19
  transition: none;
@@ -46,13 +46,19 @@
46
46
  border: none;
47
47
  border-radius: radius('md');
48
48
  background: rgb(0 0 0 / 40%);
49
- color: #efefef;
49
+ color: $fixed-surface-text;
50
50
  font-size: font-size('xl');
51
51
  line-height: 1;
52
52
  cursor: pointer;
53
53
  z-index: z-index('lift');
54
54
  user-select: none;
55
55
  transition: var(--mw-transition);
56
+ @include tappable;
57
+
58
+ &:active {
59
+ transform: translateY(-50%) scale(0.9);
60
+ transition-duration: var(--mw-duration-instant);
61
+ }
56
62
 
57
63
  &::before {
58
64
  content: '';
@@ -65,10 +71,7 @@
65
71
  background: rgb(0 0 0 / 85%);
66
72
  }
67
73
 
68
- &:focus-visible {
69
- outline: 2px solid var(--mw-primary-text-color);
70
- outline-offset: 2px;
71
- }
74
+ @include focus-ring;
72
75
 
73
76
  &-prev {
74
77
  left: spacing('4');
@@ -101,10 +104,16 @@
101
104
  display: inline-block;
102
105
  width: 11px;
103
106
  height: 11px;
104
- border-radius: 50%;
107
+ padding: 0;
108
+ border: 0;
109
+ appearance: none;
110
+ border-radius: radius('full');
105
111
  background: var(--mw-gray-color);
106
112
  cursor: pointer;
107
- transition: transform 0.4s ease;
113
+ transition: var(--mw-transition-fast);
114
+
115
+ @include tappable;
116
+ @include focus-ring;
108
117
 
109
118
  &.mw-active {
110
119
  transform: scale(1.5);