@smwb/ui-styles 1.6.0 → 2.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 (38) hide show
  1. package/README.md +157 -22
  2. package/less/components/appBar/appBar.less +108 -0
  3. package/less/components/button/button.less +7 -1
  4. package/less/components/checkbox/checkbox.less +37 -3
  5. package/less/components/chip/chip.less +15 -3
  6. package/less/components/commandPalette/commandPalette.entry.less +2 -0
  7. package/less/components/commandPalette/commandPalette.less +233 -0
  8. package/less/components/composer/composer.entry.less +2 -0
  9. package/less/components/composer/composer.less +294 -0
  10. package/less/components/composer/composer.variables.less +10 -0
  11. package/less/components/diffStat/diffStat.entry.less +2 -0
  12. package/less/components/diffStat/diffStat.less +76 -0
  13. package/less/components/kbd/kbd.entry.less +2 -0
  14. package/less/components/kbd/kbd.less +55 -0
  15. package/less/components/list/list.less +39 -0
  16. package/less/components/qrCode/qrCode.entry.less +2 -0
  17. package/less/components/qrCode/qrCode.less +35 -0
  18. package/less/components/radioButton/radioButton.less +32 -0
  19. package/less/components/selectField/selectField.less +21 -4
  20. package/less/components/sidebar/sidebar.less +26 -0
  21. package/less/components/statusDot/statusDot.entry.less +2 -0
  22. package/less/components/statusDot/statusDot.less +118 -0
  23. package/less/components/swimlanes/swimlanes.entry.less +2 -0
  24. package/less/components/swimlanes/swimlanes.less +126 -0
  25. package/less/components/tabGroups/tabGroups.entry.less +2 -0
  26. package/less/components/tabGroups/tabGroups.less +219 -0
  27. package/less/components/tabs/tab/tab.less +128 -0
  28. package/less/components/tabs/tabPanel/tabPanel.entry.less +2 -0
  29. package/less/components/tabs/tabPanel/tabPanel.less +8 -0
  30. package/less/components/tabs/tabs.less +26 -0
  31. package/less/components/textField/textField.less +34 -9
  32. package/less/components/toggle/toggle.less +28 -1
  33. package/less/components/treeView/treeView.less +40 -2
  34. package/less/components.less +9 -0
  35. package/less/default/globals/default.variables.less +79 -0
  36. package/less/foundation/sizing.less +41 -5
  37. package/less/variables.less +5 -0
  38. package/package.json +5 -2
@@ -0,0 +1,233 @@
1
+ // The ⌘K palette. It is a Modal, so its box is overridden through a
2
+ // three-class selector: `.smwb-modal.smwb-medium` is (0,2,0), and which of two
3
+ // equal-specificity sheets wins differs between a Storybook dev server and a
4
+ // build, so a two-class rule would only win by luck.
5
+ .smwb-modal__overlay > .smwb-modal.smwb-command-palette {
6
+ width: @commandPaletteWidth;
7
+ height: auto;
8
+ min-height: 0;
9
+ max-height: @commandPaletteMaxHeight;
10
+ // The results box scrolls, not the dialog, so the footer stays put.
11
+ overflow: hidden;
12
+ padding: 0;
13
+ // Modal's own radius. Its small-screen rule zeroes it for a full-bleed dialog;
14
+ // the palette keeps side gutters there instead, so it keeps the corners too.
15
+ border-radius: @modalBorderRadius;
16
+ // A cross-axis auto margin beats the overlay's `align-items: center`, which is
17
+ // what puts the palette near the top instead of in the middle.
18
+ margin-block: @commandPaletteTopOffset auto;
19
+
20
+ @media (max-width: 600px) {
21
+ margin-block-start: @smui-space-lg;
22
+ }
23
+ }
24
+
25
+ .smwb-command-palette {
26
+ &__panel {
27
+ display: flex;
28
+ flex-direction: column;
29
+ flex: 1 1 auto;
30
+ min-height: 0;
31
+ }
32
+
33
+ &__search {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: @smui-space-sm;
37
+ padding: @smui-space-md @smui-space-lg;
38
+ border-bottom: 1px solid var(--smui-dividerColor);
39
+ }
40
+
41
+ &__search-icon {
42
+ display: inline-flex;
43
+ flex-shrink: 0;
44
+ color: var(--smui-onSurfaceVariant);
45
+ }
46
+
47
+ // The caret is a text field's focus indicator, so the box draws no ring of its
48
+ // own (normalize.css removes the browser's outline anyway).
49
+ &__input {
50
+ flex: 1;
51
+ min-width: 0;
52
+ border: 0;
53
+ background: transparent;
54
+ font: inherit;
55
+ font-size: 1rem;
56
+ line-height: 1.5;
57
+ color: var(--smui-onSurfaceColor);
58
+ outline: none;
59
+
60
+ &::placeholder {
61
+ color: var(--smui-onSurfaceVariant);
62
+ opacity: 1; // the UA grey would stay light in the dark theme
63
+ }
64
+ }
65
+
66
+ &__results {
67
+ flex: 1 1 auto;
68
+ min-height: 0;
69
+ overflow-y: auto;
70
+ overscroll-behavior: contain; // a palette over a page does not scroll it
71
+ padding: @smui-space-xs 0;
72
+ }
73
+
74
+ &__listbox,
75
+ &__group-options {
76
+ list-style: none;
77
+ margin: 0;
78
+ padding: 0;
79
+ }
80
+
81
+ // As SelectField's group label, so a grouped list reads the same in both.
82
+ &__group-label {
83
+ padding: @smui-space-sm @smui-space-lg @smui-space-xs;
84
+ font-size: 12px;
85
+ font-weight: 600;
86
+ text-transform: uppercase;
87
+ letter-spacing: 0.04em;
88
+ color: var(--smui-onSurfaceVariant);
89
+ }
90
+
91
+ &__item {
92
+ position: relative; // holds the state layer
93
+ display: grid;
94
+ grid-template-columns: auto minmax(0, 1fr) auto;
95
+ align-items: center;
96
+ gap: @smui-space-md;
97
+ min-height: @commandPaletteItemMinHeight;
98
+ padding: @smui-space-xs @smui-space-lg;
99
+ margin: 0 @smui-space-xs;
100
+ border-radius: @commandPaletteItemRadius;
101
+ cursor: pointer;
102
+ color: var(--smui-onSurfaceColor);
103
+
104
+ &::before {
105
+ content: "";
106
+ position: absolute;
107
+ inset: 0;
108
+ border-radius: inherit;
109
+ background: currentColor;
110
+ opacity: 0;
111
+ pointer-events: none;
112
+ }
113
+
114
+ &:hover:not(&_active):not(&_disabled)::before {
115
+ opacity: @commandPaletteHoverLayerOpacity;
116
+ }
117
+
118
+ // The option the keys are on. Focus stays in the field, which points here
119
+ // with aria-activedescendant, so this row draws the library's ring inside
120
+ // itself: a scrolling results box would clip an outside one. The state layer
121
+ // is not an indicator on its own (1.29:1 against a plain row) and forced
122
+ // colours flatten it to Canvas, hence the outline.
123
+ &_active {
124
+ outline: 2px solid var(--smui-primaryColor);
125
+ outline-offset: -2px;
126
+
127
+ &::before {
128
+ opacity: @commandPaletteActiveLayerOpacity;
129
+ }
130
+
131
+ @media (forced-colors: active) {
132
+ outline-color: Highlight;
133
+ }
134
+ }
135
+
136
+ &_disabled {
137
+ color: var(--smui-disabledTextColor);
138
+ cursor: default;
139
+ }
140
+ }
141
+
142
+ &__item-icon {
143
+ grid-column: 1;
144
+ display: inline-flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ width: @commandPaletteIconSize;
148
+ height: @commandPaletteIconSize;
149
+ color: var(--smui-onSurfaceVariant);
150
+ }
151
+
152
+ &__item-text {
153
+ grid-column: 2;
154
+ min-width: 0; // lets the two lines ellipsis instead of widening the track
155
+ }
156
+
157
+ // With no icon the text takes the icon's column as well, so a row without one
158
+ // is not indented against an empty gutter. A browser without :has() drops the
159
+ // rule and keeps the text in column 2, which the empty `auto` track collapses.
160
+ &__item:not(:has(> .smwb-command-palette__item-icon)) > .smwb-command-palette__item-text {
161
+ grid-column: 1 / 3;
162
+ }
163
+
164
+ &__item-label {
165
+ display: block;
166
+ overflow: hidden;
167
+ text-overflow: ellipsis;
168
+ white-space: nowrap;
169
+ }
170
+
171
+ &__item-subtitle {
172
+ display: block;
173
+ overflow: hidden;
174
+ text-overflow: ellipsis;
175
+ white-space: nowrap;
176
+ font-size: 0.8125rem;
177
+ color: var(--smui-onSurfaceVariant);
178
+ }
179
+
180
+ // Two classes: Kbd sets `font-size: inherit` at the same specificity, and in
181
+ // Storybook the per-component sheets can land in either order.
182
+ &__item-shortcut.smwb-kbd {
183
+ grid-column: 3;
184
+ font-size: 0.75rem;
185
+ color: var(--smui-onSurfaceVariant);
186
+ }
187
+
188
+ // The rows a pending search draws in place of results. Skeleton is already
189
+ // aria-hidden, and the item grid keeps the widths the real rows will have, so
190
+ // the list does not reflow when the answer lands. No hover layer: nothing here
191
+ // can be chosen.
192
+ &__loading-row {
193
+ display: grid;
194
+ grid-template-columns: auto minmax(0, 1fr) auto;
195
+ align-items: center;
196
+ gap: @smui-space-md;
197
+ min-height: @commandPaletteItemMinHeight;
198
+ padding: @smui-space-xs @smui-space-lg;
199
+ margin: 0 @smui-space-xs;
200
+ }
201
+
202
+ &__message {
203
+ padding: @smui-space-lg @smui-space-xl;
204
+ text-align: center;
205
+ color: var(--smui-onSurfaceVariant);
206
+
207
+ &_error {
208
+ color: var(--smui-errorColor);
209
+ }
210
+ }
211
+
212
+ &__footer {
213
+ display: flex;
214
+ align-items: center;
215
+ gap: @smui-space-md;
216
+ padding: @smui-space-sm @smui-space-lg;
217
+ border-top: 1px solid var(--smui-dividerColor);
218
+ font-size: 0.75rem;
219
+ color: var(--smui-onSurfaceVariant);
220
+ }
221
+
222
+ // The polite live region. A screen reader ignores one outside an aria-modal
223
+ // dialog, so it lives inside the palette and is hidden from sight only. With
224
+ // no inset it stays at its static position, out of flow.
225
+ &__status {
226
+ position: absolute;
227
+ width: 1px;
228
+ height: 1px;
229
+ overflow: hidden;
230
+ clip-path: inset(50%);
231
+ white-space: nowrap;
232
+ }
233
+ }
@@ -0,0 +1,2 @@
1
+ @import (reference) "../../_prelude.less";
2
+ @import "./composer.less";
@@ -0,0 +1,294 @@
1
+ @import "./composer.variables.less";
2
+
3
+ // The textarea and its mirror (.smwb-composer__field::after) must share these,
4
+ // or the auto-grown height drifts from the text.
5
+ .smwb-composer-text() {
6
+ box-sizing: border-box;
7
+ margin: 0;
8
+ border: 0;
9
+ padding: @composerFieldPaddingY @composerFieldPaddingX;
10
+ font: inherit;
11
+ letter-spacing: inherit;
12
+ line-height: @composerLineHeight;
13
+ overflow-wrap: anywhere;
14
+ // The height of N rows of text, with the text's own padding.
15
+ min-height: ~"calc(var(--smwb-composer-min-rows, 1) * @{composerLineHeight} + 2 * @{composerFieldPaddingY})";
16
+ max-height: ~"calc(var(--smwb-composer-max-rows, 8) * @{composerLineHeight} + 2 * @{composerFieldPaddingY})";
17
+ }
18
+
19
+ // A task input: a growing textarea with an attach button, a toolbar slot, a
20
+ // counter and a send/stop button. The children are flat; grid areas place them
21
+ // for each layout. An absent child leaves its area empty, and the spacing is
22
+ // margins, not gaps, so an empty track adds none.
23
+ .smwb-composer {
24
+ position: relative;
25
+ display: grid;
26
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
27
+ align-items: center;
28
+ box-sizing: border-box;
29
+ width: 100%;
30
+ min-width: 0;
31
+ padding: @composerPadding;
32
+ // Focus (below) paints the second pixel of the border inside, so the layout
33
+ // does not shift.
34
+ border: 1px solid @composerBorder;
35
+ border-radius: @composerRadius;
36
+ background-color: @composerBg;
37
+ color: @composerText;
38
+
39
+ &__attachments {
40
+ grid-area: attachments;
41
+ min-width: 0;
42
+ margin-bottom: @composerGap;
43
+ }
44
+
45
+ &__field {
46
+ grid-area: field;
47
+ display: grid;
48
+ min-width: 0;
49
+
50
+ // The mirror: the text plus a trailing space (so a final newline counts a
51
+ // line) in the same box as the textarea. It sizes the grid cell, and the
52
+ // textarea fills it, so the field grows with the text up to max-height.
53
+ &::after {
54
+ .smwb-composer-text();
55
+ content: attr(data-value) " ";
56
+ grid-area: 1 / 1;
57
+ visibility: hidden;
58
+ pointer-events: none;
59
+ white-space: pre-wrap;
60
+ overflow: hidden;
61
+ }
62
+ }
63
+
64
+ &__input {
65
+ .smwb-composer-text();
66
+ grid-area: 1 / 1;
67
+ display: block;
68
+ width: 100%;
69
+ resize: none;
70
+ overflow-y: auto;
71
+ outline: 0;
72
+ background: transparent;
73
+ color: @composerText;
74
+ caret-color: @composerText;
75
+
76
+ &::placeholder {
77
+ // The UA grey stays light in the dark theme.
78
+ color: @composerPlaceholder;
79
+ opacity: 1;
80
+ }
81
+ }
82
+
83
+ &__icon-button {
84
+ display: inline-flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ box-sizing: border-box;
88
+ flex: none;
89
+ width: @composerIconButtonSize;
90
+ height: @composerIconButtonSize;
91
+ margin: 0;
92
+ padding: 0;
93
+ border: 0;
94
+ border-radius: @smui-radius-circle;
95
+ background-color: transparent;
96
+ color: @composerText;
97
+ font: inherit;
98
+ cursor: pointer;
99
+ user-select: none;
100
+ -webkit-user-select: none;
101
+ transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
102
+
103
+ &:hover:not(:disabled):not([aria-disabled="true"]) {
104
+ background-color: var(--smui-onSurfaceHover);
105
+ }
106
+
107
+ &:active:not(:disabled):not([aria-disabled="true"]) {
108
+ background-color: var(--smui-onSurfaceActive);
109
+ }
110
+
111
+ // normalize.css removes outlines, so each focusable draws its own ring.
112
+ &:focus-visible {
113
+ outline: 2px solid @composerBorderFocus;
114
+ outline-offset: 2px;
115
+ }
116
+
117
+ &:disabled {
118
+ cursor: default;
119
+ pointer-events: none;
120
+ }
121
+ }
122
+
123
+ &__attach {
124
+ grid-area: attach;
125
+ color: @composerCounter;
126
+ }
127
+
128
+ &__toolbar {
129
+ grid-area: toolbar;
130
+ display: flex;
131
+ flex-wrap: wrap;
132
+ align-items: center;
133
+ gap: @composerGap;
134
+ min-width: 0;
135
+ }
136
+
137
+ // The toolbar follows the attach button.
138
+ &__attach ~ &__toolbar {
139
+ margin-inline-start: @composerGap;
140
+ }
141
+
142
+ &__counter {
143
+ grid-area: counter;
144
+ margin-inline-start: @composerGap;
145
+ color: @composerCounter;
146
+ font-size: 0.75rem;
147
+ line-height: 1rem;
148
+ font-variant-numeric: tabular-nums;
149
+ white-space: nowrap;
150
+
151
+ &_error {
152
+ color: @composerCounterError;
153
+ font-weight: 500;
154
+ }
155
+ }
156
+
157
+ &__action {
158
+ grid-area: action;
159
+ margin-inline-start: @composerGap;
160
+
161
+ // Send is available: filled.
162
+ &:not([aria-disabled="true"]):not(:disabled) {
163
+ background-color: @composerActionBg;
164
+ color: @composerActionColor;
165
+
166
+ &:hover {
167
+ background-color: ~"color-mix(in srgb, @{composerActionColor} 12%, @{composerActionBg})";
168
+ }
169
+
170
+ &:active {
171
+ background-color: ~"color-mix(in srgb, @{composerActionColor} 20%, @{composerActionBg})";
172
+ }
173
+ }
174
+
175
+ // Send is unavailable (empty text, over the limit, no handler): muted. The
176
+ // button is not natively disabled, so it keeps focus.
177
+ &[aria-disabled="true"],
178
+ &:disabled {
179
+ background-color: var(--smui-disabledBgColor);
180
+ color: var(--smui-disabledTextColor);
181
+ cursor: default;
182
+ }
183
+
184
+ // Stop: the agent is busy.
185
+ &_stop:not([aria-disabled="true"]):not(:disabled) {
186
+ background-color: @composerStopBg;
187
+ color: @composerStopColor;
188
+
189
+ &:hover {
190
+ background-color: @composerStopBg;
191
+ opacity: 0.88;
192
+ }
193
+
194
+ &:active {
195
+ background-color: @composerStopBg;
196
+ opacity: 0.76;
197
+ }
198
+ }
199
+ }
200
+
201
+ // The live region for the counter: read by screen readers, never drawn.
202
+ &__status {
203
+ position: absolute;
204
+ width: 1px;
205
+ height: 1px;
206
+ margin: -1px;
207
+ padding: 0;
208
+ border: 0;
209
+ overflow: hidden;
210
+ clip-path: inset(50%);
211
+ white-space: nowrap;
212
+ }
213
+
214
+ // ---- Layouts ----
215
+ // stacked: the field on top; a bar under it (attach, toolbar, counter, action).
216
+ &_stacked {
217
+ grid-template-areas:
218
+ "attachments attachments attachments attachments"
219
+ "field field field field"
220
+ "attach toolbar counter action";
221
+
222
+ .smwb-composer__field {
223
+ margin-bottom: @composerGap;
224
+ }
225
+
226
+ .smwb-composer__action {
227
+ justify-self: end;
228
+ }
229
+ }
230
+
231
+ // inline: one row (attach, field, counter, action); the toolbar, if any, under it.
232
+ &_inline {
233
+ grid-template-areas:
234
+ "attachments attachments attachments attachments"
235
+ "attach field counter action"
236
+ "toolbar toolbar toolbar toolbar";
237
+ // Buttons stay at the bottom edge as the field grows.
238
+ align-items: end;
239
+
240
+ .smwb-composer__toolbar {
241
+ margin-top: @composerGap;
242
+ }
243
+
244
+ .smwb-composer__counter {
245
+ // On the bottom row of text, level with the action.
246
+ align-self: end;
247
+ padding-bottom: ~"calc((@{composerIconButtonSize} - 1rem) / 2)";
248
+ }
249
+
250
+ .smwb-composer__attach ~ .smwb-composer__toolbar {
251
+ margin-inline-start: 0;
252
+ }
253
+ }
254
+
255
+ // ---- States ----
256
+ // Set from the textarea's focus, not :focus-within: the chips in the toolbar
257
+ // have rings of their own.
258
+ &_focused {
259
+ border-color: @composerBorderFocus;
260
+ box-shadow: inset 0 0 0 1px @composerBorderFocus;
261
+ }
262
+
263
+ &_drag-active {
264
+ border-style: dashed;
265
+ border-color: @composerBorderFocus;
266
+ background-color: @composerDragBg;
267
+ }
268
+
269
+ &_disabled {
270
+ opacity: 0.6;
271
+ cursor: default;
272
+ }
273
+
274
+ &_over-limit:not(.smwb-composer_focused) {
275
+ border-color: @composerCounterError;
276
+ }
277
+
278
+ @media (forced-colors: active) {
279
+ &_focused {
280
+ outline: 2px solid Highlight;
281
+ outline-offset: -2px;
282
+ }
283
+
284
+ &__action:not([aria-disabled="true"]):not(:disabled) {
285
+ border: 1px solid ButtonText;
286
+ }
287
+
288
+ // The UA greys a native disabled control; an unavailable Send is aria-disabled, so it needs the same grey.
289
+ &__action[aria-disabled="true"],
290
+ &__action:disabled {
291
+ color: GrayText;
292
+ }
293
+ }
294
+ }
@@ -0,0 +1,10 @@
1
+ @composerPadding: @smui-space-sm;
2
+ @composerGap: @smui-space-xs;
3
+ @composerRadius: @smui-radius-lg;
4
+ // One text line. The textarea and its mirror share it, so their heights agree.
5
+ @composerLineHeight: 1.5rem;
6
+ // The text's own padding inside the field; with one line, 24px + 2 * 6px = 36px,
7
+ // the height of an icon button.
8
+ @composerFieldPaddingY: 6px;
9
+ @composerFieldPaddingX: @smui-space-sm;
10
+ @composerIconButtonSize: 36px;
@@ -0,0 +1,2 @@
1
+ @import (reference) "../../_prelude.less";
2
+ @import "./diffStat.less";
@@ -0,0 +1,76 @@
1
+ // The size of a change: "+N −M" (a U+2212 minus, not a hyphen), with an optional
2
+ // five-cell bar of the same proportions.
3
+ //
4
+ // Both the figures and their cells take the theme's success and error pulled
5
+ // @diffStatColorMix toward the surface ink. The raw tokens measure 3.89:1 (light)
6
+ // and 4.12–4.14:1 (dark) against the page — under the 4.5:1 text of this size
7
+ // needs — while the mix comes out 5.42:1 at worst across both themes and both
8
+ // backgrounds, and still clears the 3:1 a cell needs against a neutral one
9
+ // (WCAG 1.4.3 and 1.4.11). A cell nobody owns is an outline, not a fill: the surface
10
+ // ink mixed @diffStatNeutralMix into transparent measures 3.69:1 at worst (the material
11
+ // and summer light themes), 4.12:1 or better in the default light theme against the page
12
+ // and the surface and 5.4:1 or better in the dark ones, so "4 of 5" never looks like "5 of 5"; the tint
13
+ // the old track fill gave was 1.29:1 (default) and 1.08:1 (summer) against the page.
14
+ // No colour is written here: the ink is the theme's, so the dark theme follows on its own.
15
+ .smwb-diff-stat {
16
+ --smwb-diff-stat-added: ~"color-mix(in srgb, var(--smui-successColor) @{diffStatColorMix}, var(--smui-onSurfaceColor))";
17
+ --smwb-diff-stat-removed: ~"color-mix(in srgb, var(--smui-errorColor) @{diffStatColorMix}, var(--smui-onSurfaceColor))";
18
+ --smwb-diff-stat-neutral: ~"color-mix(in srgb, var(--smui-onSurfaceColor) @{diffStatNeutralMix}, transparent)";
19
+
20
+ display: inline-flex;
21
+ align-items: center;
22
+ gap: @diffStatGap;
23
+ min-width: 0;
24
+ font-size: @diffStatFontSize;
25
+ line-height: 1.4;
26
+ font-variant-numeric: tabular-nums;
27
+ white-space: nowrap;
28
+
29
+ &__text {
30
+ display: inline-flex;
31
+ gap: @diffStatSignGap;
32
+ font-weight: 600;
33
+ }
34
+
35
+ &__added {
36
+ color: var(--smwb-diff-stat-added);
37
+ }
38
+
39
+ &__removed {
40
+ color: var(--smwb-diff-stat-removed);
41
+ }
42
+
43
+ &__bar {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: @diffStatCellGap;
47
+ }
48
+
49
+ &__cell {
50
+ width: @diffStatCellWidth;
51
+ height: @diffStatCellHeight;
52
+ border-radius: @diffStatCellRadius;
53
+ // A border rather than a shadow, so the outline survives forced colours.
54
+ border: 1px solid var(--smwb-diff-stat-neutral);
55
+ box-sizing: border-box;
56
+ }
57
+
58
+ &__cell_added {
59
+ border-color: var(--smwb-diff-stat-added);
60
+ background-color: var(--smwb-diff-stat-added);
61
+ }
62
+
63
+ &__cell_removed {
64
+ border-color: var(--smwb-diff-stat-removed);
65
+ background-color: var(--smwb-diff-stat-removed);
66
+ }
67
+
68
+ // A tab label: the figures shrink and the cells with them.
69
+ &_compact {
70
+ font-size: @diffStatCompactFontSize;
71
+
72
+ .smwb-diff-stat__cell {
73
+ height: @diffStatCompactCellHeight;
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,2 @@
1
+ @import (reference) "../../_prelude.less";
2
+ @import "./kbd.less";
@@ -0,0 +1,55 @@
1
+ // A keyboard shortcut: one small key cap per key. Everything is drawn from
2
+ // currentColor, so the same caps sit on a menu item, on the dark tooltip and in
3
+ // dark mode alike.
4
+ .smwb-kbd {
5
+ position: relative; // holds the visually hidden text
6
+ display: inline-flex;
7
+ align-items: center;
8
+ gap: @kbdGap;
9
+ flex-shrink: 0;
10
+ font-family: inherit;
11
+ font-size: inherit;
12
+ line-height: 1;
13
+ white-space: nowrap;
14
+
15
+ &__key {
16
+ box-sizing: border-box;
17
+ display: inline-flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ min-width: @kbdKeyMinSize;
21
+ height: @kbdKeyMinSize;
22
+ padding: 0 @kbdKeyPaddingX;
23
+ border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
24
+ border-radius: @kbdKeyRadius;
25
+ background-color: color-mix(in srgb, currentColor 10%, transparent);
26
+ font-family: inherit;
27
+ font-size: inherit;
28
+ font-weight: 500;
29
+ line-height: 1;
30
+ color: inherit;
31
+ user-select: none; // the caps repeat the hidden text: copy and find see the shortcut once
32
+
33
+ @media (forced-colors: active) {
34
+ border-color: currentColor;
35
+ }
36
+ }
37
+
38
+ &__separator {
39
+ opacity: 0.7;
40
+ user-select: none;
41
+ }
42
+
43
+ // The text assistive technology reads instead of the key caps.
44
+ &__text {
45
+ position: absolute;
46
+ width: 1px;
47
+ height: 1px;
48
+ margin: -1px;
49
+ padding: 0;
50
+ overflow: hidden;
51
+ clip: rect(0 0 0 0);
52
+ white-space: nowrap;
53
+ border: 0;
54
+ }
55
+ }