@smwb/ui-styles 1.5.1 → 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 (54) hide show
  1. package/README.md +210 -12
  2. package/less/components/appBar/appBar.less +108 -0
  3. package/less/components/avatar/avatar.less +22 -0
  4. package/less/components/button/button.less +7 -1
  5. package/less/components/card/card.less +15 -4
  6. package/less/components/chart/chart.less +16 -15
  7. package/less/components/checkbox/checkbox.less +55 -4
  8. package/less/components/chip/chip.less +48 -3
  9. package/less/components/commandPalette/commandPalette.entry.less +2 -0
  10. package/less/components/commandPalette/commandPalette.less +233 -0
  11. package/less/components/composer/composer.entry.less +2 -0
  12. package/less/components/composer/composer.less +294 -0
  13. package/less/components/composer/composer.variables.less +10 -0
  14. package/less/components/dagView/dagView.entry.less +2 -0
  15. package/less/components/dagView/dagView.less +280 -0
  16. package/less/components/descriptionList/descriptionList.less +7 -1
  17. package/less/components/diffStat/diffStat.entry.less +2 -0
  18. package/less/components/diffStat/diffStat.less +76 -0
  19. package/less/components/kbd/kbd.entry.less +2 -0
  20. package/less/components/kbd/kbd.less +55 -0
  21. package/less/components/list/list.less +39 -0
  22. package/less/components/menu/menu.less +25 -1
  23. package/less/components/personChip/personChip.less +4 -2
  24. package/less/components/qrCode/qrCode.entry.less +2 -0
  25. package/less/components/qrCode/qrCode.less +35 -0
  26. package/less/components/radioButton/radioButton.less +46 -1
  27. package/less/components/rating/rating.less +15 -2
  28. package/less/components/selectField/selectField.less +87 -5
  29. package/less/components/sidebar/sidebar.less +26 -0
  30. package/less/components/statusDot/statusDot.entry.less +2 -0
  31. package/less/components/statusDot/statusDot.less +118 -0
  32. package/less/components/swimlanes/swimlanes.entry.less +2 -0
  33. package/less/components/swimlanes/swimlanes.less +126 -0
  34. package/less/components/tabGroups/tabGroups.entry.less +2 -0
  35. package/less/components/tabGroups/tabGroups.less +219 -0
  36. package/less/components/table/table.less +12 -0
  37. package/less/components/tabs/tab/tab.less +128 -0
  38. package/less/components/tabs/tabPanel/tabPanel.entry.less +2 -0
  39. package/less/components/tabs/tabPanel/tabPanel.less +8 -0
  40. package/less/components/tabs/tabs.less +26 -0
  41. package/less/components/textField/textField.less +34 -9
  42. package/less/components/thumbCard/thumbCard.less +12 -2
  43. package/less/components/toggle/toggle.less +42 -2
  44. package/less/components/treeView/treeView.less +40 -2
  45. package/less/components/typography/typography.less +13 -6
  46. package/less/components.less +10 -0
  47. package/less/connects/rt/table.less +7 -0
  48. package/less/default/globals/default.variables.less +152 -1
  49. package/less/foundation/sizing.less +41 -5
  50. package/less/foundation/typography.less +21 -0
  51. package/less/themes/material/variables.less +1 -0
  52. package/less/themes/summer/variables.less +1 -0
  53. package/less/variables.less +5 -0
  54. package/package.json +5 -2
@@ -7,8 +7,16 @@
7
7
  box-sizing: border-box;
8
8
  cursor: pointer;
9
9
 
10
+ // The content row is 24px tall (the search wrapper's min-height and the 1.5rem
11
+ // arrow icon), so the vertical padding is (token - 24px) / 2: 24 + (token - 24)
12
+ // = token exactly, and the 23px text line sits at token / 2 as the resting
13
+ // label (textField.less: (token - 23px) / 2 above a 23px line) does. A wrapped
14
+ // multi-select grows past min-height and keeps the same padding.
10
15
  &:not(&--inline) {
11
16
  padding: @selectFieldPadding;
17
+ padding-top: calc((var(--smui-control-h-md, @smui-control-h-md) - 24px) / 2);
18
+ padding-bottom: calc((var(--smui-control-h-md, @smui-control-h-md) - 24px) / 2);
19
+ min-height: var(--smui-control-h-md, @smui-control-h-md);
12
20
  }
13
21
 
14
22
  &:not(&--inline)&--filled {
@@ -44,6 +52,33 @@
44
52
  }
45
53
  }
46
54
 
55
+ // The control, the part of the field that is the button: the selected text
56
+ // and the search box. Chips sit before it and the clear button after it,
57
+ // outside, so it takes the room they leave.
58
+ &__control {
59
+ display: flex;
60
+ flex: 1 1 auto;
61
+ align-items: center;
62
+ align-self: stretch;
63
+ // The scrolling row the control sits in would cut its focus ring, so the
64
+ // ring goes around the whole field, as when the field was the control: the
65
+ // control's own is pushed out of sight, and ::after, laid over the field
66
+ // (the input holder is its containing block), takes its outline. Whatever
67
+ // sets that outline still decides it, e.g. a reset that removes focus rings
68
+ // (normalize.css) removes this one too.
69
+ &:focus {
70
+ outline-offset: 9999px;
71
+ }
72
+
73
+ &:focus::after {
74
+ content: "";
75
+ position: absolute;
76
+ inset: 0;
77
+ outline: inherit;
78
+ pointer-events: none;
79
+ }
80
+ }
81
+
47
82
  &__actions {
48
83
  display: flex;
49
84
  align-items: center;
@@ -51,6 +86,19 @@
51
86
  margin-right: -7px;
52
87
  }
53
88
 
89
+ // The clear button draws only its icon.
90
+ &__clear {
91
+ display: inline-flex;
92
+ flex-shrink: 0;
93
+ margin: 0;
94
+ padding: 0;
95
+ border: 0;
96
+ background: none;
97
+ color: inherit;
98
+ font: inherit;
99
+ cursor: pointer;
100
+ }
101
+
54
102
  &__values {
55
103
  white-space: nowrap;
56
104
  font: inherit;
@@ -61,7 +109,10 @@
61
109
  margin-left: @selectFieldChipMarginLeft;
62
110
  }
63
111
 
64
- & + span {
112
+ // The search box keeps its distance from the values: it follows the
113
+ // selected text in the control, or opens the control after the chips.
114
+ & + .smwb-select-field__search,
115
+ & + .smwb-select-field__control > .smwb-select-field__search {
65
116
  margin-left: @selectFieldChipMarginLeft;
66
117
  }
67
118
 
@@ -187,15 +238,46 @@
187
238
  color: var(--smui-primaryColor);
188
239
  }
189
240
 
241
+ // The options: a listbox, and the groups in it, inside the menu's list.
242
+ .smwb-select-field__listbox,
243
+ .smwb-select-field__group-options {
244
+ list-style: none;
245
+ margin: 0;
246
+ padding: 0;
247
+ }
248
+
249
+ // The option the keyboard is on. The focus stays on the field, which points at
250
+ // the option, so it is drawn as a menu item with the keyboard focus: the ring
251
+ // inside it (an optionRenderer's option too) and the focus state layer.
252
+ .smwb-select-field__option--active {
253
+ outline: 2px solid var(--smui-primaryColor);
254
+ outline-offset: -2px;
255
+ }
256
+
257
+ // Inside the menu, so it outranks the selected shade (.smwb-menu__item--selected)
258
+ // whichever stylesheet comes first; menu.less keeps hover off it.
259
+ .smwb-menu .smwb-menu__item.smwb-select-field__option--active:before {
260
+ opacity: @menuOpacity-focus;
261
+ }
262
+
190
263
  // size, as for TextField: the padding and the font follow the control scale, so
191
264
  // a small select stands as tall as a small text field or button in a toolbar.
192
265
  .smwb-text-field_size-small .smwb-select-field:not(.smwb-select-field--inline) {
193
- padding-top: @smui-space-sm;
194
- padding-bottom: @smui-space-sm;
266
+ padding-top: calc((var(--smui-control-h-sm, @smui-control-h-sm) - 24px) / 2);
267
+ padding-bottom: calc((var(--smui-control-h-sm, @smui-control-h-sm) - 24px) / 2);
268
+ min-height: var(--smui-control-h-sm, @smui-control-h-sm);
195
269
  font-size: @smui-control-font-sm;
196
270
  }
197
271
  .smwb-text-field_size-large .smwb-select-field:not(.smwb-select-field--inline) {
198
- padding-top: @smui-space-lg;
199
- padding-bottom: @smui-space-lg;
272
+ padding-top: calc((var(--smui-control-h-lg, @smui-control-h-lg) - 24px) / 2);
273
+ padding-bottom: calc((var(--smui-control-h-lg, @smui-control-h-lg) - 24px) / 2);
274
+ min-height: var(--smui-control-h-lg, @smui-control-h-lg);
200
275
  font-size: @smui-control-font-lg;
201
276
  }
277
+ // A filled select keeps its own padding and height (the shrunk label needs it).
278
+ .smwb-select-field--filled:not(.smwb-select-field--inline),
279
+ .smwb-text-field_size-small .smwb-select-field--filled:not(.smwb-select-field--inline),
280
+ .smwb-text-field_size-large .smwb-select-field--filled:not(.smwb-select-field--inline) {
281
+ min-height: 0;
282
+ padding: @selectFieldFilledPadding;
283
+ }
@@ -47,6 +47,27 @@
47
47
  }
48
48
  }
49
49
 
50
+ // Contained mode (the `contained` prop): an in-flow rail for side panels
51
+ // inside a page area instead of a viewport-fixed one. The rail keeps its
52
+ // variant width, does not shrink, stretches to the container's height and
53
+ // scrolls a long menu inside itself.
54
+ &.smwb-sidebar_contained {
55
+ position: relative;
56
+ top: auto;
57
+ left: auto;
58
+ height: 100%;
59
+ min-height: 0;
60
+ align-self: stretch;
61
+ flex-shrink: 0;
62
+ overflow-y: auto;
63
+ overflow-x: hidden;
64
+ // A long one-word title (e.g. the compact variant's) must not spill past
65
+ // the rail: the rail clips horizontally instead of growing a scrollbar.
66
+ .smwb-nav-item {
67
+ overflow: hidden;
68
+ }
69
+ }
70
+
50
71
  &__collapse {
51
72
  width: 100%;
52
73
  display: flex;
@@ -94,6 +115,11 @@
94
115
  flex-direction: column;
95
116
  align-items: center;
96
117
  gap: @sidebarNavGap;
118
+ // Never squash items to fit a definite-height nav (contained mode):
119
+ // a long menu must scroll inside the rail, not compress its items.
120
+ .smwb-nav-item {
121
+ flex-shrink: 0;
122
+ }
97
123
  }
98
124
 
99
125
  .smwb-nav-item {
@@ -0,0 +1,2 @@
1
+ @import (reference) "../../_prelude.less";
2
+ @import "./statusDot.less";
@@ -0,0 +1,118 @@
1
+ // A status marker: a small dot that sits beside a label in a tree node, a tab or
2
+ // a graph node. The colour comes from theme tokens only; shape tells the quiet
3
+ // statuses apart without it (idle is solid, unknown is a ring).
4
+ .smwb-status-dot {
5
+ --smwb-status-dot-color: var(--smui-onSurfaceVariant);
6
+ display: inline-block;
7
+ position: relative;
8
+ flex: none;
9
+ box-sizing: border-box;
10
+ width: @statusDotSizeMd;
11
+ height: @statusDotSizeMd;
12
+ border-radius: 50%;
13
+ background-color: var(--smwb-status-dot-color);
14
+ vertical-align: middle;
15
+
16
+ &_size-sm {
17
+ width: @statusDotSizeSm;
18
+ height: @statusDotSizeSm;
19
+ }
20
+
21
+ &_status-active {
22
+ --smwb-status-dot-color: var(--smui-infoColor);
23
+ }
24
+ &_status-attention {
25
+ --smwb-status-dot-color: var(--smui-warningColor);
26
+ }
27
+ &_status-idle {
28
+ --smwb-status-dot-color: var(--smui-onSurfaceVariant);
29
+ }
30
+ &_status-done {
31
+ --smwb-status-dot-color: var(--smui-successColor);
32
+ }
33
+ &_status-error {
34
+ --smwb-status-dot-color: var(--smui-errorColor);
35
+ }
36
+ // Not known yet: a ring, not a disc, in the outline colour.
37
+ &_status-unknown {
38
+ --smwb-status-dot-color: var(--smui-outlineColor);
39
+ background-color: transparent;
40
+ border: @statusDotRing solid var(--smwb-status-dot-color);
41
+ }
42
+
43
+ // After the statuses, so a `color` wins over the status's own.
44
+ &_color-default {
45
+ --smwb-status-dot-color: var(--smui-onSurfaceVariant);
46
+ }
47
+ &_color-primary {
48
+ --smwb-status-dot-color: var(--smui-primaryColor);
49
+ }
50
+ &_color-secondary {
51
+ --smwb-status-dot-color: var(--smui-secondaryColor);
52
+ }
53
+ &_color-error {
54
+ --smwb-status-dot-color: var(--smui-errorColor);
55
+ }
56
+ &_color-success {
57
+ --smwb-status-dot-color: var(--smui-successColor);
58
+ }
59
+ &_color-warning {
60
+ --smwb-status-dot-color: var(--smui-warningColor);
61
+ }
62
+ &_color-info {
63
+ --smwb-status-dot-color: var(--smui-infoColor);
64
+ }
65
+
66
+ // The pulse is a ring spreading from the dot. It is drawn only while motion is
67
+ // allowed, so with reduced motion the dot stays still and nothing else changes.
68
+ &_pulse::after {
69
+ content: "";
70
+ position: absolute;
71
+ inset: 0;
72
+ border-radius: inherit;
73
+ background-color: var(--smwb-status-dot-color);
74
+ opacity: 0;
75
+ pointer-events: none;
76
+ }
77
+ // `unknown` is a ring, so its pulse is a ring too, not a disc inside it.
78
+ &_status-unknown&_pulse::after {
79
+ inset: -@statusDotRing;
80
+ background-color: transparent;
81
+ border: @statusDotRing solid var(--smwb-status-dot-color);
82
+ }
83
+ }
84
+
85
+ @media (prefers-reduced-motion: no-preference) {
86
+ .smwb-status-dot_pulse::after {
87
+ animation: smwb-status-dot-pulse 1.6s ease-out infinite;
88
+ }
89
+ }
90
+
91
+ @keyframes smwb-status-dot-pulse {
92
+ 0% {
93
+ transform: scale(1);
94
+ opacity: 0.5;
95
+ }
96
+ 100% {
97
+ transform: scale(2.4);
98
+ opacity: 0;
99
+ }
100
+ }
101
+
102
+ // Forced colours drop background colours: draw the dot in the text colour.
103
+ @media (forced-colors: active) {
104
+ .smwb-status-dot {
105
+ background-color: CanvasText;
106
+ &_status-unknown {
107
+ background-color: transparent;
108
+ border-color: CanvasText;
109
+ }
110
+ &_pulse::after {
111
+ background-color: CanvasText;
112
+ }
113
+ &_status-unknown&_pulse::after {
114
+ background-color: transparent;
115
+ border-color: CanvasText;
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,2 @@
1
+ @import (reference) "../../_prelude.less";
2
+ @import "./swimlanes.less";
@@ -0,0 +1,126 @@
1
+ // Participant lanes: one row per lane, a header column fixed on the leading
2
+ // edge and a content cell that scrolls sideways under it. The root is the
3
+ // scroller; the rows are absolutely placed in a sizer, so a lane scrolled out
4
+ // of the virtual window keeps its own place instead of joining a moved band.
5
+ .smwb-swimlanes {
6
+ position: relative;
7
+ box-sizing: border-box;
8
+ width: 100%;
9
+ // The default height; the `height` prop or a height in `style` wins.
10
+ height: @swimlanesHeight;
11
+ min-width: 0;
12
+ overflow: auto;
13
+ // A fling at an edge must not scroll the page. There is no smooth scrolling:
14
+ // the keyboard pages the virtual window, and the two must agree at once.
15
+ overscroll-behavior: contain;
16
+ border: 1px solid var(--smui-dividerColor);
17
+ border-radius: @smui-radius-lg;
18
+ background-color: var(--smui-surfaceBackground);
19
+ color: var(--smui-onSurfaceColor);
20
+
21
+ // No rows to scroll: the label sits centred in the box the grid keeps.
22
+ &_empty {
23
+ display: grid;
24
+ place-items: center;
25
+ }
26
+
27
+ &__empty {
28
+ padding: @smui-space-8 @smui-space-lg;
29
+ color: var(--smui-onSurfaceVariant);
30
+ text-align: center;
31
+ }
32
+
33
+ &__sizer {
34
+ position: relative;
35
+ // The width that creates the sideways overflow: both columns at least.
36
+ min-width: calc(
37
+ var(--smwb-swimlanes-header-width, @swimlanesHeaderWidth) + var(--smwb-swimlanes-content-min-width, 0px)
38
+ );
39
+ }
40
+
41
+ &__row {
42
+ // One tint for the whole band; both cells read it.
43
+ --smwb-swimlanes-tint: transparent;
44
+
45
+ position: absolute;
46
+ right: 0;
47
+ left: 0;
48
+ display: grid;
49
+ grid-template-columns: var(--smwb-swimlanes-header-width, @swimlanesHeaderWidth) minmax(0, 1fr);
50
+ box-sizing: border-box;
51
+ // The row height is JS-only: the virtual window's maths needs it there.
52
+ height: var(--smwb-swimlanes-row-height);
53
+ border-bottom: 1px solid var(--smui-dividerColor);
54
+ }
55
+
56
+ // Hover, then selected, then disabled — all at one class's weight, so this
57
+ // order alone decides the tint a lane wears.
58
+ &__row:where(:hover) {
59
+ --smwb-swimlanes-tint: var(--smui-onSurfaceHover);
60
+ }
61
+
62
+ &__row_selected {
63
+ --smwb-swimlanes-tint: color-mix(in srgb, var(--smui-primaryColor) 12%, transparent);
64
+ }
65
+
66
+ // A disabled lane is not a target, so it takes no tint under the pointer.
67
+ &__row_disabled {
68
+ --smwb-swimlanes-tint: transparent;
69
+ color: var(--smui-disabledTextColor);
70
+ }
71
+
72
+ &__header,
73
+ &__content {
74
+ display: flex;
75
+ align-items: center;
76
+ min-width: 0;
77
+ padding: @smui-space-sm @smui-space-md;
78
+ overflow: hidden;
79
+ }
80
+
81
+ // Sticks to the leading edge while the content scrolls under it. The
82
+ // background is opaque, so what scrolls past is hidden: the tint is painted
83
+ // over the surface as a gradient layer, and both follow the theme.
84
+ &__header {
85
+ position: sticky;
86
+ inset-inline-start: 0;
87
+ z-index: 1;
88
+ background: linear-gradient(var(--smwb-swimlanes-tint), var(--smwb-swimlanes-tint)), var(--smui-surfaceBackground);
89
+ // The divider between the fixed column and the scrolling one.
90
+ box-shadow: inset -1px 0 0 var(--smui-dividerColor);
91
+ }
92
+
93
+ // The selected lane's stripe, so the selection is not the tint alone.
94
+ &__row_selected &__header {
95
+ box-shadow:
96
+ inset 3px 0 0 var(--smui-primaryColor),
97
+ inset -1px 0 0 var(--smui-dividerColor);
98
+ }
99
+
100
+ &__content {
101
+ background-color: var(--smwb-swimlanes-tint);
102
+ }
103
+
104
+ // normalize.css takes the outline off :focus, so both cells get a ring here.
105
+ // It is inset: the rows abut, and the scroller would clip an outer one.
106
+ &__header:focus-visible,
107
+ &__content:focus-visible {
108
+ outline: 2px solid var(--smui-primaryColor);
109
+ outline-offset: -2px;
110
+ }
111
+ }
112
+
113
+ // Forced colours drop backgrounds and shadows: the selection becomes an
114
+ // outline on both cells, and the sticky header stays opaque in Canvas. A
115
+ // focused cell keeps its own ring.
116
+ @media (forced-colors: active) {
117
+ .smwb-swimlanes__header {
118
+ background: Canvas;
119
+ }
120
+
121
+ .smwb-swimlanes__row_selected .smwb-swimlanes__header:not(:focus-visible),
122
+ .smwb-swimlanes__row_selected .smwb-swimlanes__content:not(:focus-visible) {
123
+ outline: 2px solid Highlight;
124
+ outline-offset: -2px;
125
+ }
126
+ }
@@ -0,0 +1,2 @@
1
+ @import (reference) "../../_prelude.less";
2
+ @import "./tabGroups.less";
@@ -0,0 +1,219 @@
1
+ // Tab groups: one CSS grid whose children are the groups' headers, the separators between them
2
+ // and the panels. The component computes the tracks and every child's `grid-area` from the
3
+ // layout (see `tabGroupsLayout.ts`), and the panels stay in the DOM in the order they first
4
+ // appeared: a move only changes where a panel is drawn, so it keeps its state, its scroll, an
5
+ // iframe in it and the focus inside it.
6
+ .smwb-tab-groups {
7
+ position: relative;
8
+ display: grid;
9
+ width: 100%;
10
+ height: 100%;
11
+ min-width: 0;
12
+ min-height: 0;
13
+ overflow: hidden;
14
+ // The separator track the component's `grid-template-*` names.
15
+ --smwb-tab-groups-separator-size: @tabGroupsSeparatorSize;
16
+
17
+ &__header {
18
+ display: flex;
19
+ align-items: stretch;
20
+ box-sizing: border-box;
21
+ min-width: 0;
22
+ border-bottom: 1px solid var(--smui-dividerColor);
23
+ }
24
+
25
+ // The group the user works in. An inset shadow, not a border: the header track is `auto` and
26
+ // shared by a row, so a border on one header would grow it past its neighbours. Drawn only
27
+ // under `--split` (two groups or more) — a single group is plain Tabs and marks nothing.
28
+ &--split &__header--active {
29
+ box-shadow: inset 0 @tabGroupsActiveLineWidth 0 0 var(--smui-primaryColor);
30
+ }
31
+
32
+ &__tabs {
33
+ flex: 1 1 auto;
34
+ min-width: 0;
35
+ }
36
+
37
+ // The group's actions button: the box of Tabs' overflow trigger, which it sits beside.
38
+ &__actions-trigger {
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ flex: 0 0 auto;
43
+ width: @tabsOverflowTriggerWidth;
44
+ padding: 0;
45
+ border: none;
46
+ background: none;
47
+ color: var(--smui-onSurfaceVariant);
48
+ cursor: pointer;
49
+
50
+ &:hover:not(:disabled) {
51
+ background-color: var(--smui-onSurfaceHover);
52
+ }
53
+ // normalize.css removes the outline: the trigger draws its own ring.
54
+ &:focus-visible {
55
+ outline: 2px solid var(--smui-primaryColor);
56
+ outline-offset: -2px;
57
+ }
58
+ &:disabled {
59
+ opacity: 0.38;
60
+ cursor: default;
61
+ }
62
+ }
63
+
64
+ // The panel is a grid child of its own (one per tab, only the selected one shown), so it
65
+ // clips and scrolls by itself; `.smwb-tab-panel` keeps the focus ring.
66
+ &__panel {
67
+ position: relative;
68
+ min-width: 0;
69
+ min-height: 0;
70
+ overflow: auto;
71
+ }
72
+
73
+ // A separator between two groups: ScreenDivider's resizer look, a grip wider than the track
74
+ // so the pointer has something to catch, and the resize the component drives from it.
75
+ &__separator {
76
+ position: relative;
77
+ z-index: 1;
78
+ box-sizing: border-box;
79
+ background: var(--smui-primaryContainer);
80
+ // A pointer-drag resize owns the touch gesture: no page scroll while it runs.
81
+ touch-action: none;
82
+ user-select: none;
83
+
84
+ // The invisible grip: the track is thin, the hit area is not.
85
+ &::before {
86
+ content: "";
87
+ position: absolute;
88
+ z-index: 1;
89
+ }
90
+
91
+ &--vertical {
92
+ border-left: thin solid var(--smui-primaryColor);
93
+ border-right: thin solid var(--smui-primaryColor);
94
+ cursor: col-resize;
95
+
96
+ &::before {
97
+ top: 0;
98
+ bottom: 0;
99
+ left: 50%;
100
+ width: @tabGroupsSeparatorHitArea;
101
+ transform: translateX(-50%);
102
+ }
103
+ }
104
+
105
+ &--horizontal {
106
+ border-top: thin solid var(--smui-primaryColor);
107
+ border-bottom: thin solid var(--smui-primaryColor);
108
+ cursor: row-resize;
109
+
110
+ &::before {
111
+ top: 50%;
112
+ right: 0;
113
+ left: 0;
114
+ height: @tabGroupsSeparatorHitArea;
115
+ transform: translateY(-50%);
116
+ }
117
+ }
118
+
119
+ &:hover,
120
+ &--dragging {
121
+ background: var(--smui-primaryColor);
122
+ }
123
+
124
+ &:focus-visible {
125
+ outline: 2px solid var(--smui-primaryColor);
126
+ outline-offset: -2px;
127
+ }
128
+ }
129
+
130
+ // The zone a dragged tab would land in, one per group and only while a drag runs. The
131
+ // element itself stays transparent and keeps its pointer events (it has to receive
132
+ // `dragover`, and it rides above the panels so an iframe in one cannot swallow it); the
133
+ // area the tab would take is painted by the pseudo-element: all of the body for `center`,
134
+ // the half beside that edge for an edge.
135
+ &__drop-target {
136
+ position: relative;
137
+ z-index: 2;
138
+
139
+ &::before {
140
+ content: "";
141
+ position: absolute;
142
+ background: color-mix(in srgb, var(--smui-primaryColor) percentage(@tabGroupsDropTintOpacity), transparent);
143
+ outline: 2px solid var(--smui-primaryColor);
144
+ outline-offset: -2px;
145
+ }
146
+
147
+ &--center::before {
148
+ inset: 0;
149
+ }
150
+ &--top::before {
151
+ top: 0;
152
+ right: 0;
153
+ left: 0;
154
+ height: 50%;
155
+ }
156
+ &--right::before {
157
+ top: 0;
158
+ right: 0;
159
+ bottom: 0;
160
+ width: 50%;
161
+ }
162
+ &--bottom::before {
163
+ right: 0;
164
+ bottom: 0;
165
+ left: 0;
166
+ height: 50%;
167
+ }
168
+ &--left::before {
169
+ top: 0;
170
+ bottom: 0;
171
+ left: 0;
172
+ width: 50%;
173
+ }
174
+ }
175
+
176
+ // Nothing open: the slot takes the whole area, whatever the grid tracks are.
177
+ &__empty {
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ grid-area: 1 / 1 / -1 / -1;
182
+ min-width: 0;
183
+ min-height: 0;
184
+ }
185
+
186
+ // The root's last Tab stop, through which the focus leaves the area (the Tab order follows the
187
+ // reading order, not the DOM's): never drawn, and never focused for longer than a key press.
188
+ // The panel's end marker: never drawn, focused only for a moment, as the point the browser's
189
+ // Shift+Tab enters the panel from.
190
+ &__panel-end {
191
+ position: absolute;
192
+ width: 0;
193
+ height: 0;
194
+ overflow: hidden;
195
+ outline: none;
196
+ }
197
+
198
+ &__focus-guard {
199
+ position: absolute;
200
+ width: 1px;
201
+ height: 1px;
202
+ overflow: hidden;
203
+ clip: rect(0 0 0 0);
204
+ outline: none;
205
+ }
206
+
207
+ // The announcement region: never drawn, always in the accessibility tree.
208
+ &__status {
209
+ position: absolute;
210
+ width: 1px;
211
+ height: 1px;
212
+ margin: -1px;
213
+ padding: 0;
214
+ overflow: hidden;
215
+ clip: rect(0 0 0 0);
216
+ white-space: nowrap;
217
+ border: 0;
218
+ }
219
+ }
@@ -64,6 +64,18 @@
64
64
  font-weight: 500;
65
65
  }
66
66
  }
67
+ // The column's controls (TableHeadCell actions) sit after the sort
68
+ // control, not in it: inside, their names and values would join its
69
+ // accessible name.
70
+ .smwb-head-cell__actions {
71
+ align-items: center;
72
+ display: inline-flex;
73
+ vertical-align: middle;
74
+ gap: @tableActionsGap;
75
+ margin-inline-start: @tableActionsGap;
76
+ // A sortable cell shows a pointer; the controls are not the sort control.
77
+ cursor: auto;
78
+ }
67
79
  &.smwb-align_left {
68
80
  text-align: left;
69
81
  }