@smwb/ui-styles 1.6.0 → 2.1.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.
- package/README.md +157 -22
- package/less/components/appBar/appBar.less +108 -0
- package/less/components/badge/badge.less +8 -0
- package/less/components/button/button.less +7 -1
- package/less/components/checkbox/checkbox.less +37 -3
- package/less/components/chip/chip.less +15 -3
- package/less/components/commandPalette/commandPalette.entry.less +2 -0
- package/less/components/commandPalette/commandPalette.less +233 -0
- package/less/components/composer/composer.entry.less +2 -0
- package/less/components/composer/composer.less +294 -0
- package/less/components/composer/composer.variables.less +10 -0
- package/less/components/diffStat/diffStat.entry.less +2 -0
- package/less/components/diffStat/diffStat.less +76 -0
- package/less/components/kbd/kbd.entry.less +2 -0
- package/less/components/kbd/kbd.less +55 -0
- package/less/components/list/list.less +39 -0
- package/less/components/qrCode/qrCode.entry.less +2 -0
- package/less/components/qrCode/qrCode.less +35 -0
- package/less/components/radioButton/radioButton.less +32 -0
- package/less/components/selectField/selectField.less +21 -4
- package/less/components/sidebar/sidebar.less +26 -0
- package/less/components/statusDot/statusDot.entry.less +2 -0
- package/less/components/statusDot/statusDot.less +118 -0
- package/less/components/swimlanes/swimlanes.entry.less +2 -0
- package/less/components/swimlanes/swimlanes.less +126 -0
- package/less/components/tabGroups/tabGroups.entry.less +2 -0
- package/less/components/tabGroups/tabGroups.less +219 -0
- package/less/components/tabs/tab/tab.less +134 -2
- package/less/components/tabs/tabPanel/tabPanel.entry.less +2 -0
- package/less/components/tabs/tabPanel/tabPanel.less +8 -0
- package/less/components/tabs/tabs.less +26 -0
- package/less/components/textField/textField.less +34 -9
- package/less/components/toggle/toggle.less +28 -1
- package/less/components/treeView/treeView.less +40 -2
- package/less/components.less +9 -0
- package/less/default/globals/default.variables.less +84 -0
- package/less/foundation/sizing.less +41 -5
- package/less/variables.less +10 -0
- 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,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,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,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
|
+
}
|