@surflane/ui 0.12.0 → 0.13.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 (126) hide show
  1. package/dist/alert-dialog.d.ts +25 -7
  2. package/dist/alert-dialog.js +9 -4
  3. package/dist/autocomplete.d.ts +2 -6
  4. package/dist/avatar.d.ts +7 -7
  5. package/dist/avatar.js +2 -2
  6. package/dist/bar-list.js +7 -1
  7. package/dist/button.d.ts +6 -11
  8. package/dist/button.js +8 -8
  9. package/dist/card.d.ts +15 -10
  10. package/dist/card.js +7 -2
  11. package/dist/chart.d.ts +6 -4
  12. package/dist/chart.js +83 -19
  13. package/dist/choice.d.ts +2 -2
  14. package/dist/class-names.d.ts +0 -4
  15. package/dist/class-names.js +0 -14
  16. package/dist/code.d.ts +19 -7
  17. package/dist/code.js +32 -38
  18. package/dist/combobox.d.ts +2 -8
  19. package/dist/combobox.js +2 -2
  20. package/dist/context-menu.d.ts +2 -8
  21. package/dist/context-menu.js +2 -2
  22. package/dist/css/rules/alert-dialog.css +4 -2
  23. package/dist/css/rules/app-shell.css +47 -10
  24. package/dist/css/rules/bar-list.css +2 -2
  25. package/dist/css/rules/bar.css +5 -12
  26. package/dist/css/rules/bottom-navigation.css +5 -2
  27. package/dist/css/rules/breadcrumbs.css +0 -6
  28. package/dist/css/rules/button.css +4 -12
  29. package/dist/css/rules/card.css +14 -0
  30. package/dist/css/rules/chart.css +13 -5
  31. package/dist/css/rules/choice.css +26 -2
  32. package/dist/css/rules/combobox.css +17 -16
  33. package/dist/css/rules/dashboard.css +53 -1
  34. package/dist/css/rules/date-picker.css +50 -27
  35. package/dist/css/rules/dialog.css +38 -9
  36. package/dist/css/rules/disclosure.css +17 -3
  37. package/dist/css/rules/field-control.css +9 -6
  38. package/dist/css/rules/file-upload.css +9 -5
  39. package/dist/css/rules/filter-bar.css +24 -1
  40. package/dist/css/rules/gauge.css +2 -2
  41. package/dist/css/rules/icon.css +0 -5
  42. package/dist/css/rules/input-group.css +23 -8
  43. package/dist/css/rules/link.css +0 -6
  44. package/dist/css/rules/menu.css +5 -2
  45. package/dist/css/rules/menubar.css +1 -1
  46. package/dist/css/rules/navigation-menu.css +31 -26
  47. package/dist/css/rules/number-field.css +8 -1
  48. package/dist/css/rules/otp-field.css +13 -3
  49. package/dist/css/rules/page-header.css +10 -0
  50. package/dist/css/rules/panel.css +51 -10
  51. package/dist/css/rules/popup.css +50 -0
  52. package/dist/css/rules/preview-card.css +0 -21
  53. package/dist/css/rules/scroll-area.css +20 -1
  54. package/dist/css/rules/select.css +4 -1
  55. package/dist/css/rules/sizing.css +15 -3
  56. package/dist/css/rules/skeleton.css +1 -1
  57. package/dist/css/rules/slider.css +16 -8
  58. package/dist/css/rules/spinner.css +1 -1
  59. package/dist/css/rules/stat.css +9 -0
  60. package/dist/css/rules/tabs.css +14 -6
  61. package/dist/css/rules/textarea.css +28 -0
  62. package/dist/css/rules/toast.css +3 -1
  63. package/dist/css/rules/toggle.css +10 -3
  64. package/dist/css/rules/toolbar.css +10 -3
  65. package/dist/css/rules/tree.css +29 -4
  66. package/dist/dashboard.d.ts +19 -7
  67. package/dist/dashboard.js +30 -5
  68. package/dist/date-picker.d.ts +32 -3
  69. package/dist/date-picker.js +43 -4
  70. package/dist/dialog.d.ts +25 -7
  71. package/dist/dialog.js +9 -4
  72. package/dist/drawer.d.ts +10 -2
  73. package/dist/drawer.js +6 -3
  74. package/dist/icon.d.ts +5 -5
  75. package/dist/icon.js +5 -3
  76. package/dist/index.d.ts +3 -3
  77. package/dist/index.js +2 -2
  78. package/dist/inline-message.d.ts +2 -2
  79. package/dist/inline-message.js +7 -2
  80. package/dist/input.d.ts +2 -2
  81. package/dist/layout.d.ts +1 -3
  82. package/dist/layout.js +2 -2
  83. package/dist/native-select.d.ts +2 -2
  84. package/dist/navigation-menu.d.ts +16 -0
  85. package/dist/navigation-menu.js +6 -1
  86. package/dist/page-header.d.ts +10 -2
  87. package/dist/page-header.js +3 -3
  88. package/dist/panel.d.ts +6 -3
  89. package/dist/panel.js +2 -2
  90. package/dist/parts.d.ts +7 -3
  91. package/dist/parts.js +12 -4
  92. package/dist/popover.d.ts +6 -6
  93. package/dist/popover.js +3 -2
  94. package/dist/preview-card.d.ts +2 -8
  95. package/dist/preview-card.js +2 -2
  96. package/dist/select.d.ts +7 -4
  97. package/dist/select.js +23 -8
  98. package/dist/sheet.d.ts +10 -2
  99. package/dist/sheet.js +6 -3
  100. package/dist/size.d.ts +8 -6
  101. package/dist/size.js +2 -7
  102. package/dist/sizing.d.ts +13 -6
  103. package/dist/sizing.js +13 -4
  104. package/dist/sparkline.d.ts +2 -2
  105. package/dist/sparkline.js +2 -2
  106. package/dist/spinner.d.ts +2 -2
  107. package/dist/spinner.js +2 -2
  108. package/dist/stat.d.ts +6 -2
  109. package/dist/stat.js +2 -2
  110. package/dist/table.d.ts +12 -4
  111. package/dist/table.js +8 -3
  112. package/dist/textarea.d.ts +6 -3
  113. package/dist/textarea.js +2 -2
  114. package/dist/time-range-picker.d.ts +2 -2
  115. package/dist/time-range-picker.js +2 -2
  116. package/dist/toggle.d.ts +2 -2
  117. package/dist/toggle.js +2 -2
  118. package/dist/toolbar.d.ts +11 -10
  119. package/dist/toolbar.js +23 -1
  120. package/dist/tooltip.d.ts +1 -3
  121. package/dist/tooltip.js +4 -2
  122. package/dist/tree.d.ts +1 -3
  123. package/dist/tree.js +2 -2
  124. package/dist/typography.d.ts +10 -3
  125. package/dist/typography.js +4 -3
  126. package/package.json +3 -3
@@ -8,7 +8,7 @@
8
8
  min-width: 176px;
9
9
  }
10
10
  .surf-menu-trigger[data-popup-open] {
11
- background: var(--surf-selected);
11
+ background-color: var(--surf-selected);
12
12
  border-color: var(--surf-control-border);
13
13
  }
14
14
  .surf-menu-item {
@@ -31,7 +31,10 @@
31
31
  overflow-wrap: anywhere;
32
32
  }
33
33
  .surf-menu-item[data-highlighted] {
34
- background: var(--surf-menu-hover-background);
34
+ background-image: linear-gradient(
35
+ var(--surf-interaction-hover-bg),
36
+ var(--surf-interaction-hover-bg)
37
+ );
35
38
  }
36
39
  .surf-menu-item[data-disabled] {
37
40
  opacity: var(--surf-control-disabled-opacity);
@@ -6,7 +6,7 @@
6
6
  .surf-menubar {
7
7
  padding: var(--surf-space-1);
8
8
  border-radius: var(--surf-radius-md);
9
- background: var(--surf-surface);
9
+ background: var(--surf-recessed);
10
10
  width: fit-content;
11
11
  max-width: 100%;
12
12
  }
@@ -20,22 +20,20 @@
20
20
  align-items: stretch;
21
21
  }
22
22
  .surf-navigation-trigger {
23
- padding-inline: var(--surf-space-3);
24
23
  font-weight: var(--surf-weight-medium);
25
24
  background: transparent;
26
25
  color: var(--surf-ink-body);
27
26
  }
28
- .surf-navigation-trigger:hover,
29
- .surf-navigation-trigger[data-popup-open] {
30
- background: var(--surf-menu-hover-background);
31
- }
27
+ /* The trigger is a ghost Button, whose hover layer it keeps; open is held, as an
28
+ open menu trigger is. */
32
29
  .surf-navigation-trigger[data-popup-open] {
30
+ background-color: var(--surf-selected);
33
31
  color: var(--surf-ink);
34
32
  }
35
33
  .surf-navigation-icon {
36
34
  display: inline-flex;
37
35
  flex: none;
38
- transition: rotate var(--surf-motion-enter) var(--surf-motion-ease);
36
+ transition: rotate var(--surf-duration-fast) var(--surf-ease-out);
39
37
  }
40
38
  .surf-navigation-icon[data-popup-open] {
41
39
  rotate: 180deg;
@@ -57,10 +55,28 @@
57
55
  }
58
56
  .surf-navigation-link:hover,
59
57
  .surf-navigation-link[data-highlighted] {
60
- background: var(--surf-menu-hover-background);
58
+ background-image: linear-gradient(
59
+ var(--surf-interaction-hover-bg),
60
+ var(--surf-interaction-hover-bg)
61
+ );
62
+ }
63
+ /* A link that names a destination and says what is there: the title at the link's
64
+ size in medium weight, the description under it in the compact role, quieter. */
65
+ .surf-navigation-link-title {
66
+ display: block;
67
+ color: var(--surf-ink);
68
+ font-weight: var(--surf-weight-medium);
69
+ }
70
+ .surf-navigation-link-description {
71
+ display: block;
72
+ margin-block-start: var(--surf-space-1);
73
+ color: var(--surf-ink-muted);
74
+ font-size: var(--surf-type-compact-size);
75
+ line-height: var(--surf-type-compact-leading);
76
+ letter-spacing: var(--surf-type-compact-tracking);
61
77
  }
62
78
  .surf-navigation-link[aria-current='page'] {
63
- background: var(--surf-selected);
79
+ background-color: var(--surf-selected);
64
80
  color: var(--surf-ink);
65
81
  font-weight: var(--surf-weight-medium);
66
82
  }
@@ -85,19 +101,6 @@
85
101
  var(--surf-font-body);
86
102
  letter-spacing: var(--surf-type-body-tracking);
87
103
  outline: none;
88
- opacity: 1;
89
- translate: 0 0;
90
- transition:
91
- opacity var(--surf-dialog-enter-duration) var(--surf-motion-ease),
92
- translate var(--surf-dialog-enter-duration)
93
- var(--surf-motion-spatial-ease);
94
- }
95
- .surf-navigation-popup[data-starting-style] {
96
- opacity: 0;
97
- translate: 0 12px;
98
- }
99
- .surf-navigation-popup[data-ending-style] {
100
- transition-duration: 0s;
101
104
  }
102
105
  .surf-navigation-viewport {
103
106
  position: relative;
@@ -122,13 +125,9 @@
122
125
  }
123
126
  }
124
127
  @media (prefers-reduced-motion: reduce) {
125
- .surf-navigation-popup,
126
128
  .surf-navigation-icon {
127
129
  transition: none;
128
130
  }
129
- .surf-navigation-popup[data-starting-style] {
130
- translate: 0 0;
131
- }
132
131
  }
133
132
  @media (forced-colors: active) {
134
133
  .surf-navigation-popup {
@@ -137,7 +136,9 @@
137
136
  border-color: CanvasText;
138
137
  }
139
138
  .surf-navigation-link[aria-current='page'] {
140
- border-color: Highlight;
139
+ background-color: Highlight;
140
+ color: HighlightText;
141
+ forced-color-adjust: none;
141
142
  }
142
143
  }
143
144
  /* Keep the floating box measurable even when the popup anchors from its bottom edge. */
@@ -159,4 +160,8 @@
159
160
  font-size: var(--surf-ui-font);
160
161
  line-height: var(--surf-ui-leading);
161
162
  }
163
+ /* The trigger is a button: it keeps the label leading. */
164
+ :where([data-ui-size], [data-ui-size] *).surf-navigation-trigger {
165
+ line-height: var(--surf-ui-label-leading);
166
+ }
162
167
  }
@@ -26,6 +26,13 @@
26
26
  text-align: center;
27
27
  font-variant-numeric: tabular-nums;
28
28
  }
29
+ /* iOS zooms the page into a focused field whose text is under 16px. */
30
+ .surf-number-input {
31
+ font-size: max(
32
+ var(--surf-control-text-min-size),
33
+ var(--surf-type-body-size)
34
+ );
35
+ }
29
36
  .surf-number-scrub {
30
37
  cursor: ew-resize;
31
38
  touch-action: none;
@@ -50,7 +57,7 @@
50
57
  }
51
58
  @layer surflane.sizes {
52
59
  :where([data-ui-size], [data-ui-size] *):is(.surf-number-input) {
53
- font-size: var(--surf-ui-font);
60
+ font-size: max(var(--surf-control-text-min-size), var(--surf-ui-font));
54
61
  line-height: var(--surf-ui-leading);
55
62
  }
56
63
  }
@@ -16,9 +16,9 @@
16
16
  flex: 1 1 auto;
17
17
  }
18
18
  .surf-otp-input {
19
- flex: 0 1 var(--surf-control-target-size);
20
- width: var(--surf-control-target-size);
21
- height: var(--surf-control-target-size);
19
+ flex: 0 1 var(--surf-size-otp-default);
20
+ width: var(--surf-size-otp-default);
21
+ height: var(--surf-size-otp-default);
22
22
  min-width: 0;
23
23
  padding: 0;
24
24
  text-align: center;
@@ -64,9 +64,19 @@
64
64
  }
65
65
  }
66
66
  @layer surflane.sizes {
67
+ /* The box follows the size; the digits keep the section role, as type roles do. */
67
68
  :where([data-ui-size], [data-ui-size] *).surf-otp-input {
68
69
  flex-basis: var(--surf-ui-otp);
69
70
  width: var(--surf-ui-otp);
70
71
  height: var(--surf-ui-otp);
72
+ font-size: var(--surf-type-section-size);
73
+ line-height: var(--surf-type-section-leading);
74
+ }
75
+ @media (max-width: 760px), (pointer: coarse) {
76
+ :where([data-ui-size], [data-ui-size] *).surf-otp-input {
77
+ flex-basis: max(var(--surf-ui-otp), var(--surf-control-target-size));
78
+ width: max(var(--surf-ui-otp), var(--surf-control-target-size));
79
+ height: max(var(--surf-ui-otp), var(--surf-control-target-size));
80
+ }
71
81
  }
72
82
  }
@@ -1,3 +1,13 @@
1
1
  /* Page header, with the styles of what it renders. */
2
2
  @layer surflane.tokens, surflane.themes, surflane.base, surflane.components,
3
3
  surflane.sizes;
4
+
5
+ @layer surflane.sizes {
6
+ /* Compact density: the copy and the actions sit 8px apart. */
7
+ :where(
8
+ [data-ui-density='compact'],
9
+ [data-ui-density='compact'] *
10
+ ).surf-page-header-content {
11
+ gap: var(--surf-ui-density-inset);
12
+ }
13
+ }
@@ -36,11 +36,13 @@
36
36
  .surf-panel-viewport[data-flush] {
37
37
  padding: 0;
38
38
  }
39
- .surf-panel-viewport[data-side='right'] {
40
- justify-content: flex-end;
39
+ /* side names a physical edge, as the slide and the squared corners do: an auto
40
+ margin pushes the panel to it whatever the writing direction. */
41
+ .surf-panel-viewport[data-side='right'] > .surf-panel {
42
+ margin-left: auto;
41
43
  }
42
- .surf-panel-viewport[data-side='left'] {
43
- justify-content: flex-start;
44
+ .surf-panel-viewport[data-side='left'] > .surf-panel {
45
+ margin-right: auto;
44
46
  }
45
47
  .surf-panel-viewport[data-side='top'] {
46
48
  align-items: flex-start;
@@ -62,6 +64,7 @@
62
64
  max-height: 100%;
63
65
  overflow: hidden;
64
66
  pointer-events: auto;
67
+ /* A plane on the backdrop, as a dialog is: forced colors draw its edge. */
65
68
  background: var(--surf-canvas);
66
69
  color: var(--surf-ink);
67
70
  border-radius: var(--surf-radius-sheet);
@@ -102,6 +105,11 @@
102
105
  --surf-panel-enter-y: calc(100% + 12px);
103
106
  max-height: min(85dvh, 100%);
104
107
  }
108
+ /* A top or bottom panel fits its content unless --surf-panel-height holds it at one
109
+ height, so it doesn't jump as a list inside changes. */
110
+ .surf-panel:is([data-side='top'], [data-side='bottom']) {
111
+ height: var(--surf-panel-height, auto);
112
+ }
105
113
  /* A flush panel squares the corners on the edge it is attached to. */
106
114
  .surf-panel[data-flush][data-side='bottom'] {
107
115
  border-bottom-left-radius: 0;
@@ -133,10 +141,18 @@
133
141
  min-height: 0;
134
142
  flex: 1;
135
143
  }
144
+ /* The body scrolls, so it clips: the room a focus ring takes moves from the head's
145
+ bottom to the body's top, and the first control's ring shows whole. */
146
+ .surf-panel {
147
+ --surf-panel-ring-room: calc(
148
+ var(--surf-focus-ring-width) + var(--surf-focus-ring-offset)
149
+ );
150
+ }
136
151
  .surf-panel-header {
137
152
  flex: none;
138
- padding-block: var(--surf-space-6) var(--surf-space-5);
139
- padding-inline: var(--surf-space-6)
153
+ padding-block: var(--surf-space-6)
154
+ calc(var(--surf-space-5) - var(--surf-panel-ring-room));
155
+ padding-inline: var(--surf-panel-inset, var(--surf-space-6))
140
156
  calc(var(--surf-control-height) + var(--surf-space-4) * 2);
141
157
  }
142
158
  .surf-panel-title {
@@ -158,11 +174,16 @@
158
174
  flex: 1 1 auto;
159
175
  min-height: 0;
160
176
  overflow: auto;
161
- padding: 0 var(--surf-space-6) var(--surf-space-6);
177
+ padding: var(--surf-space-6) var(--surf-panel-inset, var(--surf-space-6));
178
+ /* A control scrolled into view keeps room from the clipped edge. */
179
+ scroll-padding-block: var(--surf-space-4);
162
180
  overscroll-behavior: contain;
163
181
  scrollbar-width: thin;
164
182
  scrollbar-color: var(--surf-scrollbar-thumb) var(--surf-scrollbar-track);
165
183
  }
184
+ .surf-panel-header + .surf-panel-body {
185
+ padding-top: var(--surf-panel-ring-room);
186
+ }
166
187
  .surf-panel-body::-webkit-scrollbar {
167
188
  width: var(--surf-scrollbar-size);
168
189
  height: var(--surf-scrollbar-size);
@@ -180,9 +201,20 @@
180
201
  justify-content: flex-end;
181
202
  flex-wrap: wrap;
182
203
  gap: var(--surf-space-2);
183
- padding: var(--surf-space-4) var(--surf-space-6)
204
+ padding: var(--surf-space-4) var(--surf-panel-inset, var(--surf-space-6))
184
205
  max(var(--surf-space-5), env(safe-area-inset-bottom));
185
206
  background: var(--surf-surface);
207
+ --surf-plane: var(--surf-surface);
208
+ --surf-recessed: var(--surf-canvas);
209
+ }
210
+ /* Stacked: each action takes the full width, one under the other, within reach
211
+ of a thumb on a phone. */
212
+ .surf-panel-footer[data-stacked] {
213
+ display: grid;
214
+ grid-template-columns: minmax(0, 1fr);
215
+ }
216
+ .surf-panel-footer[data-stacked] > * {
217
+ inline-size: 100%;
186
218
  }
187
219
  .surf-panel-close {
188
220
  position: absolute;
@@ -208,6 +240,12 @@
208
240
  .surf-panel-viewport[data-side='bottom'] {
209
241
  bottom: var(--drawer-keyboard-inset, 0px);
210
242
  }
243
+ /* Forced colors drop the backdrop tint and the shadow, so the edge is a line. */
244
+ @media (forced-colors: active) {
245
+ .surf-panel {
246
+ border: 1px solid CanvasText;
247
+ }
248
+ }
211
249
  }
212
250
  @layer surflane.sizes {
213
251
  :where([data-ui-size], [data-ui-size] *):is(
@@ -215,13 +253,16 @@
215
253
  .surf-panel-body,
216
254
  .surf-panel-footer
217
255
  ) {
218
- padding-inline: var(--surf-ui-card-padding);
256
+ padding-inline: var(--surf-panel-inset, var(--surf-ui-card-padding));
219
257
  }
220
258
  /* The title keeps clear of the close button: its 16px inset, its width and a 16px gap.
221
259
  * Above and below, the head follows the size's card padding: 24/20px by default. */
222
260
  :where([data-ui-size], [data-ui-size] *).surf-panel-header {
223
261
  padding-block: var(--surf-ui-card-padding)
224
- calc(var(--surf-ui-card-padding) - var(--surf-space-1));
262
+ calc(
263
+ var(--surf-ui-card-padding) - var(--surf-space-1) -
264
+ var(--surf-panel-ring-room)
265
+ );
225
266
  padding-inline-end: calc(
226
267
  var(--surf-control-height) + var(--surf-space-4) * 2
227
268
  );
@@ -25,4 +25,54 @@
25
25
  letter-spacing: var(--surf-type-body-tracking);
26
26
  outline: none;
27
27
  }
28
+ /* One entrance for every popup: it fades in and rises --surf-motion-lift in
29
+ --surf-duration-fast, and closes at once, so closing never makes the reader
30
+ wait. Dialogs, sheets and toasts move on their own tokens. */
31
+ :is(
32
+ .surf-select-popup,
33
+ .surf-popover,
34
+ .surf-menu,
35
+ .surf-combobox-popup,
36
+ .surf-preview-card,
37
+ .surf-navigation-popup
38
+ ) {
39
+ translate: 0 0;
40
+ transition:
41
+ opacity var(--surf-duration-fast) var(--surf-ease-out),
42
+ translate var(--surf-duration-fast) var(--surf-ease-out);
43
+ }
44
+ :is(
45
+ .surf-select-popup,
46
+ .surf-popover,
47
+ .surf-menu,
48
+ .surf-combobox-popup,
49
+ .surf-preview-card,
50
+ .surf-navigation-popup
51
+ )[data-starting-style] {
52
+ opacity: 0;
53
+ translate: 0 var(--surf-motion-lift);
54
+ }
55
+ :is(
56
+ .surf-select-popup,
57
+ .surf-popover,
58
+ .surf-menu,
59
+ .surf-combobox-popup,
60
+ .surf-preview-card,
61
+ .surf-navigation-popup
62
+ ):is([data-ending-style], [data-instant]) {
63
+ transition: none;
64
+ }
65
+ @media (prefers-reduced-motion: reduce) {
66
+ :is(
67
+ .surf-select-popup,
68
+ .surf-popover,
69
+ .surf-menu,
70
+ .surf-combobox-popup,
71
+ .surf-preview-card,
72
+ .surf-navigation-popup
73
+ ) {
74
+ transition: none;
75
+ translate: none;
76
+ }
77
+ }
28
78
  }
@@ -23,11 +23,6 @@
23
23
  letter-spacing: var(--surf-type-body-tracking);
24
24
  overflow-wrap: anywhere;
25
25
  outline: none;
26
- transform: translateY(0);
27
- opacity: 1;
28
- transition:
29
- opacity var(--surf-dialog-enter-duration) var(--surf-ease-out),
30
- transform var(--surf-dialog-enter-duration) var(--surf-ease-out);
31
26
  }
32
27
  .surf-preview-card > :first-child {
33
28
  margin-top: 0;
@@ -35,22 +30,6 @@
35
30
  .surf-preview-card > :last-child {
36
31
  margin-bottom: 0;
37
32
  }
38
- .surf-preview-card[data-starting-style] {
39
- opacity: 0;
40
- transform: translateY(6px);
41
- }
42
- .surf-preview-card[data-ending-style] {
43
- transition-duration: 0s;
44
- }
45
- .surf-preview-card[data-instant] {
46
- transition-duration: 0s;
47
- }
48
- @media (prefers-reduced-motion: reduce) {
49
- .surf-preview-card {
50
- transition: none;
51
- transform: none;
52
- }
53
- }
54
33
  @media (forced-colors: active) {
55
34
  .surf-preview-card {
56
35
  border-color: CanvasText;
@@ -40,6 +40,18 @@
40
40
  #000 var(--surf-space-6)
41
41
  );
42
42
  }
43
+ /* Start and end are logical: right to left, the end that hides content is the
44
+ left edge, and the start the right. */
45
+ .surf-scroll-root:dir(rtl)[data-overflow-x-end] > .surf-scroll-viewport {
46
+ mask-image: linear-gradient(
47
+ to right,
48
+ transparent,
49
+ #000 var(--surf-space-6)
50
+ );
51
+ }
52
+ .surf-scroll-root:dir(rtl)[data-overflow-x-start] > .surf-scroll-viewport {
53
+ mask-image: linear-gradient(to left, transparent, #000 var(--surf-space-6));
54
+ }
43
55
  .surf-scroll-root[data-overflow-x-start][data-overflow-x-end]
44
56
  > .surf-scroll-viewport {
45
57
  mask-image: linear-gradient(
@@ -82,13 +94,15 @@
82
94
  left: 0;
83
95
  right: 0;
84
96
  }
97
+ /* The vertical bar sits at the inline end: the right, or the left right to left,
98
+ where a page's own scrollbar sits. */
85
99
  .surf-scrollbar[data-orientation='vertical'] {
86
100
  flex-direction: column;
87
101
  width: calc(var(--surf-scrollbar-indicator-size) + 4px);
88
102
  position: absolute;
89
103
  top: 0;
90
104
  bottom: 0;
91
- right: 0;
105
+ inset-inline-end: 0;
92
106
  }
93
107
  .surf-scroll-thumb {
94
108
  flex: none;
@@ -107,6 +121,11 @@
107
121
  pointer-events: none;
108
122
  }
109
123
  }
124
+ @media (prefers-reduced-motion: reduce) {
125
+ .surf-scrollbar {
126
+ transition: none;
127
+ }
128
+ }
110
129
  @media (forced-colors: active) {
111
130
  .surf-scrollbar {
112
131
  opacity: 1;
@@ -74,7 +74,10 @@
74
74
  overflow-wrap: anywhere;
75
75
  }
76
76
  .surf-select-item[data-highlighted] {
77
- background: var(--surf-menu-hover-background);
77
+ background-image: linear-gradient(
78
+ var(--surf-interaction-hover-bg),
79
+ var(--surf-interaction-hover-bg)
80
+ );
78
81
  }
79
82
  .surf-select-item[data-selected] {
80
83
  font-weight: var(--surf-weight-medium);
@@ -21,6 +21,7 @@
21
21
  --surf-ui-button-padding: var(--surf-size-button-padding-small);
22
22
  --surf-ui-font: var(--surf-size-font-small);
23
23
  --surf-ui-leading: var(--surf-size-leading-small);
24
+ --surf-ui-label-leading: var(--surf-size-label-leading-small);
24
25
  --surf-ui-choice: var(--surf-size-choice-small);
25
26
  --surf-ui-switch-width: var(--surf-size-switch-width-small);
26
27
  --surf-ui-switch-height: var(--surf-size-switch-height-small);
@@ -64,6 +65,7 @@
64
65
  --surf-ui-button-padding: var(--surf-size-button-padding-default);
65
66
  --surf-ui-font: var(--surf-size-font-default);
66
67
  --surf-ui-leading: var(--surf-size-leading-default);
68
+ --surf-ui-label-leading: var(--surf-size-label-leading-default);
67
69
  --surf-ui-choice: var(--surf-size-choice-default);
68
70
  --surf-ui-switch-width: var(--surf-size-switch-width-default);
69
71
  --surf-ui-switch-height: var(--surf-size-switch-height-default);
@@ -107,6 +109,7 @@
107
109
  --surf-ui-button-padding: var(--surf-size-button-padding-large);
108
110
  --surf-ui-font: var(--surf-size-font-large);
109
111
  --surf-ui-leading: var(--surf-size-leading-large);
112
+ --surf-ui-label-leading: var(--surf-size-label-leading-large);
110
113
  --surf-ui-choice: var(--surf-size-choice-large);
111
114
  --surf-ui-switch-width: var(--surf-size-switch-width-large);
112
115
  --surf-ui-switch-height: var(--surf-size-switch-height-large);
@@ -138,15 +141,24 @@
138
141
  --surf-control-switch-thumb: var(--surf-ui-switch-thumb);
139
142
  }
140
143
  /* Coarse pointers retain comfortable targets even with a compact visual scheme. */
144
+ /* Compact density: blocks sit 12px apart and 8px inside, and touch keeps a
145
+ control at its size, no smaller than the 24px WCAG 2.2 AA target. */
146
+ [data-ui-density='compact'] {
147
+ --surf-ui-density-gap: var(--surf-space-3);
148
+ --surf-ui-density-inset: var(--surf-space-2);
149
+ --surf-control-target-size: var(--surf-control-target-size-min);
150
+ }
141
151
  @media (max-width: 760px), (pointer: coarse) {
142
- [data-ui-size] {
152
+ [data-ui-size],
153
+ [data-ui-density='compact'] {
154
+ /* A density scope outside any size scope takes the default height. */
143
155
  --surf-control-height: max(
144
156
  var(--surf-control-target-size),
145
- var(--surf-ui-control-height)
157
+ var(--surf-ui-control-height, var(--surf-size-control-height-default))
146
158
  );
147
159
  --surf-ui-row: max(
148
160
  var(--surf-control-target-size),
149
- var(--surf-ui-control-height)
161
+ var(--surf-ui-control-height, var(--surf-size-control-height-default))
150
162
  );
151
163
  }
152
164
  }
@@ -12,7 +12,7 @@
12
12
  /* Its own tokens: --surf-surface is about 1.07:1 on a light canvas, and a pulse
13
13
  through opacity faded it further. The pulse moves between the two shades. */
14
14
  background: var(--surf-skeleton-base);
15
- animation: surf-skeleton-pulse 1.4s ease-in-out infinite;
15
+ animation: surf-skeleton-pulse var(--surf-motion-pulse) ease-in-out infinite;
16
16
  }
17
17
  @keyframes surf-skeleton-pulse {
18
18
  50% {
@@ -40,10 +40,10 @@
40
40
  width: 100%;
41
41
  height: var(--surf-size-slider-track-default);
42
42
  border-radius: var(--surf-radius-full);
43
- background: var(--surf-slider-track);
43
+ background: var(--surf-recessed);
44
44
  }
45
- /* The pair Progress uses: a brand-mark fill on the surface track, 3:1 against both
46
- the track and the sheet in either theme. */
45
+ /* The pair Progress uses: a brand-mark fill on the recessed track, 3:1 against the
46
+ track and the plane in either theme. */
47
47
  .surf-slider-indicator {
48
48
  background: var(--surf-brand-mark);
49
49
  border-radius: inherit;
@@ -61,7 +61,8 @@
61
61
  outline: var(--surf-focus-ring-width) solid var(--surf-interaction-focus);
62
62
  outline-offset: var(--surf-focus-ring-offset);
63
63
  }
64
- .surf-slider[data-disabled] {
64
+ /* Disabled fades the control only: the label and value stay readable. */
65
+ .surf-slider-control[data-disabled] {
65
66
  opacity: var(--surf-control-disabled-opacity);
66
67
  }
67
68
  .surf-slider-control[data-disabled],
@@ -80,13 +81,14 @@
80
81
  touch-action: pan-x;
81
82
  }
82
83
  .surf-slider-track[data-orientation='vertical'] {
83
- width: 6px;
84
+ width: var(--surf-size-slider-track-default);
84
85
  height: 100%;
85
86
  }
87
+ /* A phone takes the large thumb at every size. */
86
88
  @media (max-width: 760px) {
87
89
  .surf-slider-thumb {
88
- width: 28px;
89
- height: 28px;
90
+ width: var(--surf-size-slider-thumb-large);
91
+ height: var(--surf-size-slider-thumb-large);
90
92
  }
91
93
  }
92
94
  @media (forced-colors: active) {
@@ -107,6 +109,12 @@
107
109
  width: var(--surf-ui-slider-thumb);
108
110
  height: var(--surf-ui-slider-thumb);
109
111
  }
112
+ @media (max-width: 760px) {
113
+ :where([data-ui-size], [data-ui-size] *).surf-slider-thumb {
114
+ width: var(--surf-size-slider-thumb-large);
115
+ height: var(--surf-size-slider-thumb-large);
116
+ }
117
+ }
110
118
  @media (max-width: 760px), (pointer: coarse) {
111
119
  :where([data-ui-size], [data-ui-size] *).surf-slider-control {
112
120
  min-block-size: var(--surf-control-target-size);
@@ -115,7 +123,7 @@
115
123
  :where([data-ui-size], [data-ui-size] *).surf-slider-track:not(
116
124
  [data-orientation='vertical']
117
125
  ) {
118
- height: var(--surf-ui-gauge);
126
+ height: var(--surf-ui-slider-track);
119
127
  }
120
128
  :where(
121
129
  [data-ui-size],
@@ -16,7 +16,7 @@
16
16
  .surf-spinner > svg {
17
17
  width: 100%;
18
18
  height: 100%;
19
- animation: surf-feedback-spin 1s linear infinite;
19
+ animation: surf-feedback-spin var(--surf-motion-spin) linear infinite;
20
20
  }
21
21
  @keyframes surf-feedback-spin {
22
22
  to {
@@ -114,3 +114,12 @@
114
114
  color: var(--surf-ink-body);
115
115
  }
116
116
  }
117
+ @layer surflane.sizes {
118
+ /* Compact density: values of a group sit 8px apart in a row and 12px across. */
119
+ :where(
120
+ [data-ui-density='compact'],
121
+ [data-ui-density='compact'] *
122
+ ).surf-stat-group-items {
123
+ gap: var(--surf-ui-density-inset) var(--surf-ui-density-gap);
124
+ }
125
+ }