@surflane/ui 0.11.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 (137) hide show
  1. package/dist/alert-dialog.d.ts +25 -7
  2. package/dist/alert-dialog.js +9 -4
  3. package/dist/app-shell.d.ts +10 -0
  4. package/dist/app-shell.js +7 -1
  5. package/dist/autocomplete.d.ts +2 -6
  6. package/dist/avatar.d.ts +7 -7
  7. package/dist/avatar.js +2 -2
  8. package/dist/bar-list.d.ts +31 -0
  9. package/dist/bar-list.js +31 -0
  10. package/dist/bottom-navigation.d.ts +6 -2
  11. package/dist/bottom-navigation.js +2 -2
  12. package/dist/button.d.ts +6 -11
  13. package/dist/button.js +8 -8
  14. package/dist/card.d.ts +15 -10
  15. package/dist/card.js +7 -2
  16. package/dist/chart.d.ts +58 -3
  17. package/dist/chart.js +330 -48
  18. package/dist/choice.d.ts +2 -2
  19. package/dist/class-names.d.ts +0 -4
  20. package/dist/class-names.js +0 -14
  21. package/dist/code.d.ts +19 -7
  22. package/dist/code.js +32 -38
  23. package/dist/combobox.d.ts +2 -8
  24. package/dist/combobox.js +2 -2
  25. package/dist/context-menu.d.ts +2 -8
  26. package/dist/context-menu.js +2 -2
  27. package/dist/css/bar-list.css +9 -0
  28. package/dist/css/chart.css +2 -0
  29. package/dist/css/dashboard.css +20 -0
  30. package/dist/css/rules/alert-dialog.css +8 -2
  31. package/dist/css/rules/app-shell.css +57 -10
  32. package/dist/css/rules/bar-list.css +101 -0
  33. package/dist/css/rules/bar.css +5 -12
  34. package/dist/css/rules/bottom-navigation.css +21 -2
  35. package/dist/css/rules/breadcrumbs.css +0 -6
  36. package/dist/css/rules/button.css +4 -12
  37. package/dist/css/rules/card.css +19 -0
  38. package/dist/css/rules/chart.css +87 -8
  39. package/dist/css/rules/choice.css +26 -2
  40. package/dist/css/rules/combobox.css +17 -16
  41. package/dist/css/rules/dashboard.css +118 -0
  42. package/dist/css/rules/date-picker.css +50 -27
  43. package/dist/css/rules/dialog.css +47 -7
  44. package/dist/css/rules/disclosure.css +17 -3
  45. package/dist/css/rules/field-control.css +9 -6
  46. package/dist/css/rules/file-upload.css +9 -5
  47. package/dist/css/rules/filter-bar.css +32 -2
  48. package/dist/css/rules/gauge.css +2 -2
  49. package/dist/css/rules/icon.css +0 -5
  50. package/dist/css/rules/input-group.css +23 -8
  51. package/dist/css/rules/link.css +0 -6
  52. package/dist/css/rules/menu.css +5 -2
  53. package/dist/css/rules/menubar.css +1 -1
  54. package/dist/css/rules/navigation-menu.css +31 -26
  55. package/dist/css/rules/number-field.css +8 -1
  56. package/dist/css/rules/otp-field.css +13 -3
  57. package/dist/css/rules/page-header.css +10 -0
  58. package/dist/css/rules/panel.css +59 -10
  59. package/dist/css/rules/popup.css +50 -0
  60. package/dist/css/rules/preview-card.css +0 -21
  61. package/dist/css/rules/scroll-area.css +20 -1
  62. package/dist/css/rules/select.css +29 -2
  63. package/dist/css/rules/sizing.css +25 -3
  64. package/dist/css/rules/skeleton.css +1 -1
  65. package/dist/css/rules/slider.css +16 -8
  66. package/dist/css/rules/spinner.css +1 -1
  67. package/dist/css/rules/stat.css +66 -0
  68. package/dist/css/rules/tabs.css +14 -6
  69. package/dist/css/rules/textarea.css +28 -0
  70. package/dist/css/rules/time-range-picker.css +14 -0
  71. package/dist/css/rules/toast.css +3 -1
  72. package/dist/css/rules/toggle.css +10 -3
  73. package/dist/css/rules/toolbar.css +10 -3
  74. package/dist/css/rules/tree.css +29 -4
  75. package/dist/css/styles.css +3 -0
  76. package/dist/css/time-range-picker.css +14 -0
  77. package/dist/dashboard.d.ts +58 -0
  78. package/dist/dashboard.js +60 -0
  79. package/dist/date-picker.d.ts +32 -3
  80. package/dist/date-picker.js +43 -4
  81. package/dist/dialog.d.ts +25 -7
  82. package/dist/dialog.js +9 -4
  83. package/dist/drawer.d.ts +10 -2
  84. package/dist/drawer.js +6 -3
  85. package/dist/icon.d.ts +15 -5
  86. package/dist/icon.js +16 -3
  87. package/dist/index.d.ts +9 -6
  88. package/dist/index.js +7 -4
  89. package/dist/inline-message.d.ts +2 -2
  90. package/dist/inline-message.js +7 -2
  91. package/dist/input.d.ts +2 -2
  92. package/dist/layout.d.ts +1 -3
  93. package/dist/layout.js +2 -2
  94. package/dist/native-select.d.ts +2 -2
  95. package/dist/navigation-menu.d.ts +16 -0
  96. package/dist/navigation-menu.js +6 -1
  97. package/dist/page-header.d.ts +10 -2
  98. package/dist/page-header.js +3 -3
  99. package/dist/panel.d.ts +6 -3
  100. package/dist/panel.js +2 -2
  101. package/dist/parts.d.ts +7 -3
  102. package/dist/parts.js +12 -4
  103. package/dist/popover.d.ts +6 -6
  104. package/dist/popover.js +3 -2
  105. package/dist/preview-card.d.ts +2 -8
  106. package/dist/preview-card.js +2 -2
  107. package/dist/select.d.ts +38 -8
  108. package/dist/select.js +110 -22
  109. package/dist/sheet.d.ts +10 -2
  110. package/dist/sheet.js +6 -3
  111. package/dist/size.d.ts +8 -6
  112. package/dist/size.js +2 -7
  113. package/dist/sizing.d.ts +13 -6
  114. package/dist/sizing.js +13 -4
  115. package/dist/sparkline.d.ts +2 -2
  116. package/dist/sparkline.js +2 -2
  117. package/dist/spinner.d.ts +2 -2
  118. package/dist/spinner.js +2 -2
  119. package/dist/stat.d.ts +24 -0
  120. package/dist/stat.js +15 -1
  121. package/dist/table.d.ts +12 -4
  122. package/dist/table.js +8 -3
  123. package/dist/textarea.d.ts +6 -3
  124. package/dist/textarea.js +2 -2
  125. package/dist/time-range-picker.d.ts +42 -0
  126. package/dist/time-range-picker.js +69 -0
  127. package/dist/toggle.d.ts +2 -2
  128. package/dist/toggle.js +2 -2
  129. package/dist/toolbar.d.ts +11 -10
  130. package/dist/toolbar.js +23 -1
  131. package/dist/tooltip.d.ts +1 -3
  132. package/dist/tooltip.js +4 -2
  133. package/dist/tree.d.ts +1 -3
  134. package/dist/tree.js +2 -2
  135. package/dist/typography.d.ts +10 -3
  136. package/dist/typography.js +4 -3
  137. package/package.json +18 -3
@@ -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,12 +253,23 @@
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
- /* The title keeps clear of the close button: its 16px inset, its width and a 16px gap. */
258
+ /* The title keeps clear of the close button: its 16px inset, its width and a 16px gap.
259
+ * Above and below, the head follows the size's card padding: 24/20px by default. */
221
260
  :where([data-ui-size], [data-ui-size] *).surf-panel-header {
261
+ padding-block: var(--surf-ui-card-padding)
262
+ calc(
263
+ var(--surf-ui-card-padding) - var(--surf-space-1) -
264
+ var(--surf-panel-ring-room)
265
+ );
222
266
  padding-inline-end: calc(
223
267
  var(--surf-control-height) + var(--surf-space-4) * 2
224
268
  );
225
269
  }
270
+ :where([data-ui-size], [data-ui-size] *).surf-panel-title {
271
+ font-size: var(--surf-ui-title-size);
272
+ line-height: var(--surf-ui-title-leading);
273
+ letter-spacing: var(--surf-ui-title-tracking);
274
+ }
226
275
  }
@@ -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;
@@ -11,11 +11,35 @@
11
11
  text-align: start;
12
12
  cursor: pointer;
13
13
  }
14
- .surf-select-trigger > span:first-child {
14
+ .surf-select-trigger > span:first-child,
15
+ .surf-select-value {
15
16
  overflow: hidden;
16
17
  text-overflow: ellipsis;
17
18
  white-space: nowrap;
18
19
  }
20
+ /* Content width: the trigger fits its longest option and keeps that width. */
21
+ .surf-select[data-width='content'],
22
+ .surf-select[data-width='content'] .surf-select-trigger {
23
+ width: auto;
24
+ }
25
+ .surf-select-sizer {
26
+ display: grid;
27
+ min-width: 0;
28
+ }
29
+ .surf-select-sizer > * {
30
+ grid-area: 1 / 1;
31
+ overflow: hidden;
32
+ text-overflow: ellipsis;
33
+ white-space: nowrap;
34
+ }
35
+ .surf-select-sizer > [aria-hidden='true'] {
36
+ visibility: hidden;
37
+ }
38
+ .surf-select-separator {
39
+ block-size: 1px;
40
+ margin-block: var(--surf-space-1);
41
+ background: var(--surf-line);
42
+ }
19
43
  .surf-select-icon {
20
44
  display: flex;
21
45
  align-items: center;
@@ -50,7 +74,10 @@
50
74
  overflow-wrap: anywhere;
51
75
  }
52
76
  .surf-select-item[data-highlighted] {
53
- background: var(--surf-menu-hover-background);
77
+ background-image: linear-gradient(
78
+ var(--surf-interaction-hover-bg),
79
+ var(--surf-interaction-hover-bg)
80
+ );
54
81
  }
55
82
  .surf-select-item[data-selected] {
56
83
  font-weight: var(--surf-weight-medium);
@@ -11,12 +11,17 @@
11
11
  --surf-ui-slider-track: var(--surf-size-slider-track-small);
12
12
  --surf-ui-file-padding: var(--surf-size-file-padding-small);
13
13
  --surf-ui-content-gap: var(--surf-size-content-gap-small);
14
+ /* Surface titles (Card, Sheet, Dialog) step down one role at Small. */
15
+ --surf-ui-title-size: var(--surf-type-subsection-size);
16
+ --surf-ui-title-leading: var(--surf-type-subsection-leading);
17
+ --surf-ui-title-tracking: var(--surf-type-subsection-tracking);
14
18
 
15
19
  --surf-ui-control-height: var(--surf-size-control-height-small);
16
20
  --surf-ui-control-padding: var(--surf-size-control-padding-small);
17
21
  --surf-ui-button-padding: var(--surf-size-button-padding-small);
18
22
  --surf-ui-font: var(--surf-size-font-small);
19
23
  --surf-ui-leading: var(--surf-size-leading-small);
24
+ --surf-ui-label-leading: var(--surf-size-label-leading-small);
20
25
  --surf-ui-choice: var(--surf-size-choice-small);
21
26
  --surf-ui-switch-width: var(--surf-size-switch-width-small);
22
27
  --surf-ui-switch-height: var(--surf-size-switch-height-small);
@@ -51,12 +56,16 @@
51
56
  --surf-ui-slider-track: var(--surf-size-slider-track-default);
52
57
  --surf-ui-file-padding: var(--surf-size-file-padding-default);
53
58
  --surf-ui-content-gap: var(--surf-size-content-gap-default);
59
+ --surf-ui-title-size: var(--surf-type-section-size);
60
+ --surf-ui-title-leading: var(--surf-type-section-leading);
61
+ --surf-ui-title-tracking: var(--surf-type-section-tracking);
54
62
 
55
63
  --surf-ui-control-height: var(--surf-size-control-height-default);
56
64
  --surf-ui-control-padding: var(--surf-size-control-padding-default);
57
65
  --surf-ui-button-padding: var(--surf-size-button-padding-default);
58
66
  --surf-ui-font: var(--surf-size-font-default);
59
67
  --surf-ui-leading: var(--surf-size-leading-default);
68
+ --surf-ui-label-leading: var(--surf-size-label-leading-default);
60
69
  --surf-ui-choice: var(--surf-size-choice-default);
61
70
  --surf-ui-switch-width: var(--surf-size-switch-width-default);
62
71
  --surf-ui-switch-height: var(--surf-size-switch-height-default);
@@ -91,12 +100,16 @@
91
100
  --surf-ui-slider-track: var(--surf-size-slider-track-large);
92
101
  --surf-ui-file-padding: var(--surf-size-file-padding-large);
93
102
  --surf-ui-content-gap: var(--surf-size-content-gap-large);
103
+ --surf-ui-title-size: var(--surf-type-section-size);
104
+ --surf-ui-title-leading: var(--surf-type-section-leading);
105
+ --surf-ui-title-tracking: var(--surf-type-section-tracking);
94
106
 
95
107
  --surf-ui-control-height: var(--surf-size-control-height-large);
96
108
  --surf-ui-control-padding: var(--surf-size-control-padding-large);
97
109
  --surf-ui-button-padding: var(--surf-size-button-padding-large);
98
110
  --surf-ui-font: var(--surf-size-font-large);
99
111
  --surf-ui-leading: var(--surf-size-leading-large);
112
+ --surf-ui-label-leading: var(--surf-size-label-leading-large);
100
113
  --surf-ui-choice: var(--surf-size-choice-large);
101
114
  --surf-ui-switch-width: var(--surf-size-switch-width-large);
102
115
  --surf-ui-switch-height: var(--surf-size-switch-height-large);
@@ -128,15 +141,24 @@
128
141
  --surf-control-switch-thumb: var(--surf-ui-switch-thumb);
129
142
  }
130
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
+ }
131
151
  @media (max-width: 760px), (pointer: coarse) {
132
- [data-ui-size] {
152
+ [data-ui-size],
153
+ [data-ui-density='compact'] {
154
+ /* A density scope outside any size scope takes the default height. */
133
155
  --surf-control-height: max(
134
156
  var(--surf-control-target-size),
135
- var(--surf-ui-control-height)
157
+ var(--surf-ui-control-height, var(--surf-size-control-height-default))
136
158
  );
137
159
  --surf-ui-row: max(
138
160
  var(--surf-control-target-size),
139
- var(--surf-ui-control-height)
161
+ var(--surf-ui-control-height, var(--surf-size-control-height-default))
140
162
  );
141
163
  }
142
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 {
@@ -49,6 +49,35 @@
49
49
  .surf-stat-change[data-tone='down'] {
50
50
  color: var(--surf-trend-down-fg);
51
51
  }
52
+ /* The value's state: the tone's icon and text, on the value's line. */
53
+ .surf-stat-status {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: var(--surf-space-1);
57
+ color: var(--surf-status-neutral-fg);
58
+ font-size: var(--surf-type-label-size);
59
+ line-height: var(--surf-type-label-leading);
60
+ letter-spacing: var(--surf-type-label-tracking);
61
+ font-weight: var(--surf-weight-medium);
62
+ }
63
+ .surf-stat-status[data-tone='info'] {
64
+ color: var(--surf-status-info-fg);
65
+ }
66
+ .surf-stat-status[data-tone='success'] {
67
+ color: var(--surf-status-success-fg);
68
+ }
69
+ .surf-stat-status[data-tone='progress'] {
70
+ color: var(--surf-status-progress-fg);
71
+ }
72
+ /* Past a threshold the value takes the status color as well. */
73
+ .surf-stat-status[data-tone='caution'],
74
+ .surf-stat:has(> .surf-stat-status[data-tone='caution']) > .surf-stat-value {
75
+ color: var(--surf-status-caution-fg);
76
+ }
77
+ .surf-stat-status[data-tone='danger'],
78
+ .surf-stat:has(> .surf-stat-status[data-tone='danger']) > .surf-stat-value {
79
+ color: var(--surf-status-danger-fg);
80
+ }
52
81
  .surf-stat-caption {
53
82
  flex: 1 0 100%;
54
83
  color: var(--surf-ink-muted);
@@ -56,4 +85,41 @@
56
85
  line-height: var(--surf-type-caption-leading);
57
86
  letter-spacing: var(--surf-type-caption-tracking);
58
87
  }
88
+ /* A group: the shared label, then each value under its own name, side by side. */
89
+ .surf-stat-group {
90
+ display: grid;
91
+ gap: var(--surf-space-2);
92
+ min-width: 0;
93
+ }
94
+ .surf-stat-group-label {
95
+ color: var(--surf-ink-muted);
96
+ font-size: var(--surf-type-label-size);
97
+ line-height: var(--surf-type-label-leading);
98
+ letter-spacing: var(--surf-type-label-tracking);
99
+ }
100
+ /* Each value takes its own width and wraps to a new line when the row is full. */
101
+ .surf-stat-group-items {
102
+ display: flex;
103
+ flex-wrap: wrap;
104
+ gap: var(--surf-space-3) var(--surf-space-6);
105
+ min-width: 0;
106
+ }
107
+ /* In a group each value stacks its lines, so it is only as wide as the widest. */
108
+ .surf-stat-group-items > .surf-stat {
109
+ display: grid;
110
+ justify-items: start;
111
+ align-content: start;
112
+ }
113
+ .surf-stat-group-items .surf-stat-label {
114
+ color: var(--surf-ink-body);
115
+ }
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
+ }
59
125
  }
@@ -14,7 +14,7 @@
14
14
  min-width: 0;
15
15
  max-width: 100%;
16
16
  padding: var(--surf-space-1);
17
- background: var(--surf-surface);
17
+ background: var(--surf-recessed);
18
18
  border-radius: var(--surf-radius-md);
19
19
  overflow: auto;
20
20
  scrollbar-width: thin;
@@ -38,15 +38,23 @@
38
38
  white-space: nowrap;
39
39
  outline: none;
40
40
  }
41
+ /* State layers paint over the tab's own fill, so hovering or pressing the
42
+ selected tab keeps it selected. */
41
43
  .surf-tabs-tab:hover:not(:disabled):not([data-disabled]) {
42
44
  color: var(--surf-ink);
43
- background: var(--surf-interaction-hover-bg);
45
+ background-image: linear-gradient(
46
+ var(--surf-interaction-hover-bg),
47
+ var(--surf-interaction-hover-bg)
48
+ );
44
49
  }
45
50
  .surf-tabs-tab:active:not(:disabled):not([data-disabled]) {
46
- background: var(--surf-interaction-active-bg);
51
+ background-image: linear-gradient(
52
+ var(--surf-interaction-active-bg),
53
+ var(--surf-interaction-active-bg)
54
+ );
47
55
  }
48
56
  .surf-tabs-tab[data-active] {
49
- background: var(--surf-canvas);
57
+ background-color: var(--surf-plane);
50
58
  color: var(--surf-ink);
51
59
  font-weight: var(--surf-weight-medium);
52
60
  }
@@ -77,12 +85,12 @@
77
85
  height: var(--active-tab-height);
78
86
  box-sizing: border-box;
79
87
  border: 0;
80
- background: var(--surf-canvas);
88
+ background: var(--surf-plane);
81
89
  border-radius: var(--surf-control-radius);
82
90
  pointer-events: none;
83
91
  }
84
92
  .surf-tabs-list:has(.surf-tabs-indicator) .surf-tabs-tab[data-active] {
85
- background: transparent;
93
+ background-color: transparent;
86
94
  border-color: transparent;
87
95
  }
88
96
  .surf-tabs[data-orientation='vertical'] {
@@ -13,6 +13,34 @@
13
13
  scrollbar-width: thin;
14
14
  scrollbar-color: var(--surf-scrollbar-thumb) var(--surf-scrollbar-track);
15
15
  }
16
+ /* Embedded: the pane it fills draws the boundary, so the editor keeps the pane's
17
+ plane and no edge of its own. An outside halo would fall on the pane's edge,
18
+ so focus is a ring inside it, and an invalid value an edge inside it. */
19
+ .surf-textarea[data-variant='embedded'] {
20
+ inline-size: 100%;
21
+ padding: var(--surf-space-4);
22
+ border: 0;
23
+ border-radius: 0;
24
+ background-color: transparent;
25
+ resize: none;
26
+ }
27
+ .surf-textarea[data-variant='embedded']:focus {
28
+ background-color: transparent;
29
+ box-shadow: inset 0 0 0 var(--surf-focus-ring-width)
30
+ var(--surf-control-focus);
31
+ }
32
+ .surf-textarea[data-variant='embedded'][aria-invalid='true'] {
33
+ box-shadow: inset 0 0 0 1px var(--surf-status-danger-fg);
34
+ }
35
+ .surf-textarea[data-variant='embedded'][aria-invalid='true']:focus {
36
+ box-shadow: inset 0 0 0 var(--surf-focus-ring-width)
37
+ var(--surf-status-danger-fg);
38
+ }
39
+ @media (forced-colors: active) {
40
+ .surf-textarea[data-variant='embedded']:focus {
41
+ outline-offset: var(--surf-focus-ring-inset);
42
+ }
43
+ }
16
44
  }
17
45
  @layer surflane.sizes {
18
46
  :where([data-ui-size], [data-ui-size] *).surf-textarea {
@@ -0,0 +1,14 @@
1
+ /* Time range picker, with the styles of what it renders. */
2
+ @layer surflane.tokens, surflane.themes, surflane.base, surflane.components,
3
+ surflane.sizes;
4
+
5
+ @layer surflane.components {
6
+ /* The span, the interval and the button sit on one line in a header or toolbar. */
7
+ .surf-time-range {
8
+ display: inline-flex;
9
+ flex-wrap: wrap;
10
+ align-items: center;
11
+ gap: var(--surf-space-2);
12
+ max-inline-size: 100%;
13
+ }
14
+ }
@@ -32,7 +32,9 @@
32
32
  box-sizing: border-box;
33
33
  width: 100%;
34
34
  padding: var(--surf-space-4);
35
- border: 0;
35
+ /* No backdrop sets a toast apart from the same-tone page, and its shadow fades
36
+ in the dark theme: it keeps the floating edge. */
37
+ border: 1px solid var(--surf-menu-border);
36
38
  border-radius: var(--surf-radius-lg);
37
39
  background: var(--surf-menu-background);
38
40
  color: var(--surf-ink);