@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
@@ -28,19 +28,10 @@
28
28
  var(--surf-font-body);
29
29
  letter-spacing: var(--surf-type-body-tracking);
30
30
  outline: none;
31
- transition:
32
- opacity var(--surf-duration-fast) var(--surf-ease-out),
33
- transform var(--surf-duration-fast) var(--surf-ease-out);
34
31
  }
35
- .surf-combobox-popup[data-starting-style] {
36
- opacity: 0;
37
- transform: translateY(4px);
38
- }
39
- .surf-combobox-popup[data-ending-style] {
40
- opacity: 0;
41
- transition: none;
42
- }
43
- .surf-combobox-list {
32
+ /* In its popup the list scrolls itself. An inline list leaves scrolling to the
33
+ container it is placed in, such as a ScrollArea. */
34
+ .surf-combobox-popup .surf-combobox-list {
44
35
  max-height: min(280px, var(--available-height));
45
36
  overflow: auto;
46
37
  }
@@ -61,11 +52,16 @@
61
52
  outline: none;
62
53
  overflow-wrap: anywhere;
63
54
  }
55
+ /* The highlight is a layer over the selected fill, so the keyboard position
56
+ shows on a chosen option too. */
64
57
  .surf-combobox-item[data-highlighted] {
65
- background: var(--surf-menu-hover-background);
58
+ background-image: linear-gradient(
59
+ var(--surf-interaction-hover-bg),
60
+ var(--surf-interaction-hover-bg)
61
+ );
66
62
  }
67
63
  .surf-combobox-item[data-selected] {
68
- background: var(--surf-selected);
64
+ background-color: var(--surf-selected);
69
65
  }
70
66
  .surf-combobox-item[data-disabled] {
71
67
  opacity: var(--surf-control-disabled-opacity);
@@ -117,7 +113,7 @@
117
113
  gap: var(--surf-space-1);
118
114
  padding-inline-start: var(--surf-space-3);
119
115
  border-radius: var(--surf-radius-pill);
120
- background: var(--surf-selected);
116
+ background-color: var(--surf-selected);
121
117
  color: var(--surf-ink);
122
118
  font-size: var(--surf-type-caption-size);
123
119
  line-height: var(--surf-type-caption-leading);
@@ -143,7 +139,12 @@
143
139
  }
144
140
  }
145
141
  @media (forced-colors: active) {
146
- .surf-combobox-item:is([data-selected], [data-highlighted]) {
142
+ .surf-combobox-item[data-highlighted] {
143
+ outline: 1px solid CanvasText;
144
+ outline-offset: -1px;
145
+ }
146
+ /* A chosen value keeps its chip edge when its fill is gone. */
147
+ .surf-combobox-chip {
147
148
  outline: 1px solid CanvasText;
148
149
  outline-offset: -1px;
149
150
  }
@@ -6,14 +6,35 @@
6
6
  /* As many columns as fit a panel's minimum width; one on a phone. */
7
7
  .surf-dashboard {
8
8
  --surf-dashboard-panel-min-width: 20rem;
9
+ --surf-dashboard-gap: var(--surf-space-4);
9
10
  display: grid;
10
11
  grid-template-columns: repeat(
11
12
  auto-fill,
12
13
  minmax(min(100%, var(--surf-dashboard-panel-min-width)), 1fr)
13
14
  );
14
- gap: var(--surf-space-4);
15
+ gap: var(--surf-dashboard-gap);
15
16
  min-inline-size: 0;
16
17
  }
18
+ /* columns caps the count: a column is never narrower than its share of that many. */
19
+ .surf-dashboard[data-columns] {
20
+ grid-template-columns: repeat(
21
+ auto-fill,
22
+ minmax(
23
+ min(
24
+ 100%,
25
+ max(
26
+ var(--surf-dashboard-panel-min-width),
27
+ (
28
+ 100% - (var(--surf-dashboard-columns) - 1) *
29
+ var(--surf-dashboard-gap)
30
+ ) /
31
+ var(--surf-dashboard-columns)
32
+ )
33
+ ),
34
+ 1fr
35
+ )
36
+ );
37
+ }
17
38
  /* The header keeps its height; the content takes the rest of a stretched row. */
18
39
  .surf-dashboard-panel {
19
40
  grid-template-rows: auto minmax(0, 1fr);
@@ -42,6 +63,15 @@
42
63
  overflow: hidden;
43
64
  text-overflow: ellipsis;
44
65
  white-space: nowrap;
66
+ border-radius: var(--surf-radius-sm);
67
+ }
68
+ /* The title takes focus after Retry; a keyboard reader sees where they are. */
69
+ .surf-dashboard-panel-header .surf-card-title:focus-visible {
70
+ outline: var(--surf-focus-ring-width) solid var(--surf-interaction-focus);
71
+ outline-offset: var(--surf-focus-ring-offset);
72
+ }
73
+ .surf-dashboard-panel-header .surf-card-title:focus:not(:focus-visible) {
74
+ outline: none;
45
75
  }
46
76
  .surf-dashboard-panel-actions {
47
77
  display: flex;
@@ -53,6 +83,12 @@
53
83
  min-inline-size: 0;
54
84
  min-block-size: 0;
55
85
  }
86
+ /* A stale panel keeps its last content under the caution. */
87
+ .surf-dashboard-panel[data-state='stale'] > .surf-dashboard-panel-body {
88
+ display: grid;
89
+ align-content: start;
90
+ gap: var(--surf-space-3);
91
+ }
56
92
  .surf-dashboard-panel-empty {
57
93
  margin: 0;
58
94
  color: var(--surf-ink-muted);
@@ -64,3 +100,19 @@
64
100
  }
65
101
  }
66
102
  }
103
+ @layer surflane.sizes {
104
+ /* Compact density: panels sit 12px apart. The card inside tightens itself. */
105
+ :where(
106
+ [data-ui-density='compact'],
107
+ [data-ui-density='compact'] *
108
+ ).surf-dashboard {
109
+ --surf-dashboard-gap: var(--surf-ui-density-gap);
110
+ }
111
+ :where(
112
+ [data-ui-density='compact'],
113
+ [data-ui-density='compact'] *
114
+ ).surf-dashboard-panel[data-state='stale']
115
+ > .surf-dashboard-panel-body {
116
+ gap: var(--surf-ui-density-inset);
117
+ }
118
+ }
@@ -31,7 +31,10 @@
31
31
  }
32
32
  :is(.surf-date-picker, .surf-time-field):not([data-disabled], [data-readonly])
33
33
  .surf-date-group:hover:not([data-focus-within], :focus-within) {
34
- background: var(--surf-control-hover-background);
34
+ background-image: linear-gradient(
35
+ var(--surf-interaction-hover-bg),
36
+ var(--surf-interaction-hover-bg)
37
+ );
35
38
  border-color: var(--surf-control-hover-border);
36
39
  }
37
40
  .surf-date-group[data-focus-within],
@@ -55,14 +58,14 @@
55
58
  :is(.surf-date-picker, .surf-time-field)[data-disabled] .surf-date-group {
56
59
  opacity: var(--surf-control-disabled-opacity);
57
60
  cursor: not-allowed;
58
- background: var(--surf-control-disabled-background);
61
+ background: var(--surf-recessed);
59
62
  }
60
- /* Read-only shows a value, not a box to type in: it sits on the surface plane
63
+ /* Read-only shows a value, not a box to type in: it sits on the recessed step
61
64
  without the field's border, as text fields do. */
62
65
  :is(.surf-date-picker, .surf-time-field)[data-readonly]:not([data-invalid])
63
66
  .surf-date-group:not([data-focus-within], :focus-within) {
64
67
  border-color: transparent;
65
- background: var(--surf-surface);
68
+ background: var(--surf-recessed);
66
69
  }
67
70
  .surf-date-input {
68
71
  display: flex;
@@ -73,6 +76,11 @@
73
76
  font: var(--surf-weight-regular)
74
77
  var(--surf-type-body-size)/var(--surf-type-body-leading)
75
78
  var(--surf-font-body);
79
+ /* iOS zooms the page into a focused segment whose text is under 16px. */
80
+ font-size: max(
81
+ var(--surf-control-text-min-size),
82
+ var(--surf-type-body-size)
83
+ );
76
84
  white-space: nowrap;
77
85
  }
78
86
  .surf-date-segment {
@@ -88,10 +96,12 @@
88
96
  .surf-date-segment[data-placeholder] {
89
97
  color: var(--surf-control-placeholder);
90
98
  }
99
+ /* The group carries the focus halo; the segment being typed into is marked
100
+ like a selected table row, a selected fill told apart by its 3:1 edge. */
91
101
  .surf-date-segment[data-focused] {
92
- background: var(--surf-selected);
102
+ background-color: var(--surf-selected);
93
103
  color: var(--surf-ink);
94
- outline: 1px solid var(--surf-control-focus);
104
+ box-shadow: inset 0 0 0 1px var(--surf-selected-edge);
95
105
  }
96
106
  .surf-date-range-inputs {
97
107
  display: flex;
@@ -128,13 +138,19 @@
128
138
  :is(.surf-date-trigger, .surf-calendar-nav)[data-hovered]:not(
129
139
  [data-disabled]
130
140
  ) {
131
- background: var(--surf-selected);
141
+ background-image: linear-gradient(
142
+ var(--surf-interaction-hover-bg),
143
+ var(--surf-interaction-hover-bg)
144
+ );
132
145
  }
133
146
  :is(.surf-date-trigger, .surf-calendar-nav)[data-focus-visible] {
134
147
  outline: var(--surf-focus-ring-width) solid var(--surf-interaction-focus);
135
148
  outline-offset: var(--surf-focus-ring-offset);
136
149
  }
137
- :is(.surf-date-trigger, .surf-calendar-nav)[data-disabled] {
150
+ /* A disabled field has faded its trigger with the group already. */
151
+ :is(.surf-date-trigger, .surf-calendar-nav)[data-disabled]:not(
152
+ [data-disabled] *
153
+ ) {
138
154
  opacity: var(--surf-control-disabled-opacity);
139
155
  cursor: not-allowed;
140
156
  }
@@ -145,9 +161,9 @@
145
161
  overflow: auto;
146
162
  overscroll-behavior: contain;
147
163
  padding: var(--surf-space-2);
148
- border: 1px solid var(--surf-line);
164
+ border: 1px solid var(--surf-menu-border);
149
165
  border-radius: var(--surf-radius-lg);
150
- background: var(--surf-canvas);
166
+ background: var(--surf-menu-background);
151
167
  color: var(--surf-ink);
152
168
  box-shadow: var(--surf-shadow-float);
153
169
  z-index: var(--surf-layer-popup);
@@ -204,19 +220,19 @@
204
220
  outline: none;
205
221
  font-variant-numeric: tabular-nums;
206
222
  }
207
- .surf-calendar-day[data-hovered]:not(
208
- [data-disabled],
209
- [data-unavailable],
210
- [data-selected]
211
- ) {
212
- background: var(--surf-hover);
223
+ /* Hover is a layer, over a selected day's fill too. */
224
+ .surf-calendar-day[data-hovered]:not([data-disabled], [data-unavailable]) {
225
+ background-image: linear-gradient(
226
+ var(--surf-interaction-hover-bg),
227
+ var(--surf-interaction-hover-bg)
228
+ );
213
229
  }
214
230
  .surf-calendar-day[data-selected] {
215
- background: var(--surf-selected);
231
+ background-color: var(--surf-selected);
216
232
  color: var(--surf-ink);
217
233
  }
218
234
  .surf-calendar-day:is([data-selection-start], [data-selection-end]) {
219
- background: var(--surf-brand-mark);
235
+ background-color: var(--surf-brand-mark);
220
236
  color: var(--surf-on-brand);
221
237
  }
222
238
  .surf-calendar-day[data-focus-visible] {
@@ -238,12 +254,12 @@
238
254
  @media (max-width: 760px), (pointer: coarse) {
239
255
  .surf-date-trigger,
240
256
  .surf-calendar-nav {
241
- width: 40px;
242
- height: 40px;
243
- min-width: 40px;
257
+ width: var(--surf-control-target-size);
258
+ height: var(--surf-control-target-size);
259
+ min-width: var(--surf-control-target-size);
244
260
  }
245
261
  .surf-calendar-day {
246
- min-height: 40px;
262
+ min-height: var(--surf-control-target-size);
247
263
  }
248
264
  }
249
265
  @media (prefers-reduced-motion: reduce) {
@@ -267,10 +283,12 @@
267
283
  color: HighlightText;
268
284
  forced-color-adjust: none;
269
285
  }
270
- .surf-calendar-day[data-focus-visible],
271
- .surf-date-segment[data-focused] {
286
+ .surf-calendar-day[data-focus-visible] {
272
287
  outline-color: Highlight;
273
288
  }
289
+ .surf-date-segment[data-focused] {
290
+ outline: 1px solid Highlight;
291
+ }
274
292
  }
275
293
  .surf-calendar-grid :is(th, td) {
276
294
  padding: 0;
@@ -278,8 +296,13 @@
278
296
  min-width: 0;
279
297
  text-align: center;
280
298
  }
281
- .surf-calendar-day[data-focus-visible][data-selected] {
282
- outline-color: var(--surf-ink);
299
+ /* The ring sits inside the day. On a range end's brand fill the focus color
300
+ can match the fill, so it takes the color written on that fill. */
301
+ .surf-calendar-day:is(
302
+ [data-selection-start],
303
+ [data-selection-end]
304
+ )[data-focus-visible] {
305
+ outline-color: var(--surf-on-brand);
283
306
  }
284
307
  }
285
308
  @layer surflane.sizes {
@@ -287,7 +310,7 @@
287
310
  .surf-date-input,
288
311
  .surf-date-segment
289
312
  ) {
290
- font-size: var(--surf-ui-font);
313
+ font-size: max(var(--surf-control-text-min-size), var(--surf-ui-font));
291
314
  line-height: var(--surf-ui-leading);
292
315
  }
293
316
  :where([data-ui-size], [data-ui-size] *):is(.surf-date-trigger) > svg {
@@ -36,9 +36,11 @@
36
36
  .surf-dialog-backdrop[data-starting-style] {
37
37
  opacity: 0;
38
38
  }
39
+ /* Closing fades only, in --surf-dialog-exit-duration (0ms: at once). Dropping the
40
+ rise from the transition cancels one still running, so nothing waits on it. */
39
41
  .surf-dialog-popup[data-ending-style],
40
42
  .surf-dialog-backdrop[data-ending-style] {
41
- transition: none;
43
+ transition-property: opacity;
42
44
  transition-duration: var(--surf-dialog-exit-duration);
43
45
  opacity: 0;
44
46
  }
@@ -68,7 +70,9 @@
68
70
  overflow: hidden;
69
71
  scrollbar-gutter: auto;
70
72
  padding: 0;
71
- border: 1px solid var(--surf-line);
73
+ /* A modal is a plane on the backdrop: the backdrop and the shadow set it apart,
74
+ and forced colors draw its edge. */
75
+ border: 0;
72
76
  border-radius: var(--surf-radius-sheet);
73
77
  background: var(--surf-canvas);
74
78
  color: var(--surf-ink);
@@ -89,20 +93,27 @@
89
93
  flex: none;
90
94
  align-self: stretch;
91
95
  }
96
+ /* Planes, as in Sheet and Drawer: no line under the head; the body scrolls and
97
+ clips, so a focus ring's room moves from the head's bottom to the body's top. */
98
+ .surf-dialog-popup {
99
+ --surf-dialog-ring-room: calc(
100
+ var(--surf-focus-ring-width) + var(--surf-focus-ring-offset)
101
+ );
102
+ }
92
103
  .surf-dialog-header {
93
- padding-block: var(--surf-space-6) var(--surf-space-5);
104
+ padding-block: var(--surf-space-6)
105
+ calc(var(--surf-space-5) - var(--surf-dialog-ring-room));
94
106
  padding-inline: var(--surf-space-6)
95
107
  calc(var(--surf-control-height) + var(--surf-space-4) * 2);
96
- border-bottom: 1px solid var(--surf-line);
97
108
  }
98
- .surf-dialog-header h2 {
109
+ .surf-dialog-title {
99
110
  margin: 0;
100
111
  font: var(--surf-weight-medium)
101
112
  var(--surf-type-section-size)/var(--surf-type-section-leading)
102
113
  var(--surf-font-display);
103
114
  letter-spacing: var(--surf-type-section-tracking);
104
115
  }
105
- .surf-dialog-header p {
116
+ .surf-dialog-description {
106
117
  margin: var(--surf-space-2) 0 0;
107
118
  color: var(--surf-ink-body);
108
119
  }
@@ -115,6 +126,9 @@
115
126
  scrollbar-width: thin;
116
127
  scrollbar-color: var(--surf-scrollbar-thumb) var(--surf-scrollbar-track);
117
128
  }
129
+ .surf-dialog-header + .surf-dialog-body {
130
+ padding-top: var(--surf-dialog-ring-room);
131
+ }
118
132
  .surf-dialog-body::-webkit-scrollbar {
119
133
  width: var(--surf-scrollbar-size);
120
134
  height: var(--surf-scrollbar-size);
@@ -132,7 +146,9 @@
132
146
  flex-wrap: wrap;
133
147
  gap: var(--surf-space-2);
134
148
  padding: var(--surf-space-5) var(--surf-space-6);
135
- border-top: 1px solid var(--surf-line);
149
+ background: var(--surf-surface);
150
+ --surf-plane: var(--surf-surface);
151
+ --surf-recessed: var(--surf-canvas);
136
152
  }
137
153
  .surf-dialog-close:disabled {
138
154
  opacity: var(--surf-control-disabled-opacity);
@@ -147,11 +163,21 @@
147
163
  .surf-dialog-content[data-size='large'] {
148
164
  --surf-dialog-width: var(--surf-size-dialog-large);
149
165
  }
166
+ /* Forced colors drop the backdrop tint and the shadow, so the edge is a line. */
167
+ @media (forced-colors: active) {
168
+ .surf-dialog-popup {
169
+ border: 1px solid CanvasText;
170
+ }
171
+ }
150
172
  }
151
173
  @layer surflane.sizes {
152
174
  :where([data-ui-size], [data-ui-size] *).surf-dialog-body {
153
175
  padding: var(--surf-ui-card-padding);
154
176
  }
177
+ :where([data-ui-size], [data-ui-size] *).surf-dialog-header
178
+ + .surf-dialog-body {
179
+ padding-top: var(--surf-dialog-ring-room);
180
+ }
155
181
  :where([data-ui-size], [data-ui-size] *):is(
156
182
  .surf-dialog-header,
157
183
  .surf-dialog-footer
@@ -161,7 +187,10 @@
161
187
  /* Head and footer follow the size's card padding: 24/20px and 20px by default. */
162
188
  :where([data-ui-size], [data-ui-size] *).surf-dialog-header {
163
189
  padding-block: var(--surf-ui-card-padding)
164
- calc(var(--surf-ui-card-padding) - var(--surf-space-1));
190
+ calc(
191
+ var(--surf-ui-card-padding) - var(--surf-space-1) -
192
+ var(--surf-dialog-ring-room)
193
+ );
165
194
  padding-inline-end: calc(
166
195
  var(--surf-control-height) + var(--surf-space-4) * 2
167
196
  );
@@ -169,7 +198,7 @@
169
198
  :where([data-ui-size], [data-ui-size] *).surf-dialog-footer {
170
199
  padding-block: calc(var(--surf-ui-card-padding) - var(--surf-space-1));
171
200
  }
172
- :where([data-ui-size], [data-ui-size] *).surf-dialog-header h2 {
201
+ :where([data-ui-size], [data-ui-size] *).surf-dialog-title {
173
202
  font-size: var(--surf-ui-title-size);
174
203
  line-height: var(--surf-ui-title-leading);
175
204
  letter-spacing: var(--surf-ui-title-tracking);
@@ -8,9 +8,17 @@
8
8
  border-radius: var(--surf-radius-md);
9
9
  min-width: 0;
10
10
  }
11
+ /* The open item is recessed one step from its plane, and what it holds sits on
12
+ that step: the pair passes to its children swapped. */
11
13
  .surf-accordion-item[data-open],
12
14
  .surf-collapsible[data-open] {
13
- background: var(--surf-surface);
15
+ background: var(--surf-recessed);
16
+ --surf-disclosure-plane: var(--surf-recessed);
17
+ --surf-disclosure-recessed: var(--surf-plane);
18
+ }
19
+ :is(.surf-accordion-item, .surf-collapsible)[data-open] > * {
20
+ --surf-plane: var(--surf-disclosure-plane);
21
+ --surf-recessed: var(--surf-disclosure-recessed);
14
22
  }
15
23
  .surf-disclosure-trigger {
16
24
  display: flex;
@@ -36,10 +44,16 @@
36
44
  overflow-wrap: anywhere;
37
45
  }
38
46
  .surf-disclosure-trigger:hover:not(:disabled) {
39
- background: var(--surf-interaction-hover-bg);
47
+ background-image: linear-gradient(
48
+ var(--surf-interaction-hover-bg),
49
+ var(--surf-interaction-hover-bg)
50
+ );
40
51
  }
41
52
  .surf-disclosure-trigger:active:not(:disabled) {
42
- background: var(--surf-interaction-active-bg);
53
+ background-image: linear-gradient(
54
+ var(--surf-interaction-active-bg),
55
+ var(--surf-interaction-active-bg)
56
+ );
43
57
  }
44
58
  .surf-disclosure-trigger:focus-visible {
45
59
  outline: var(--surf-focus-ring-width) solid var(--surf-interaction-focus);
@@ -46,7 +46,10 @@
46
46
  [aria-disabled='true'],
47
47
  :focus-visible
48
48
  ) {
49
- background: var(--surf-control-hover-background);
49
+ background-image: linear-gradient(
50
+ var(--surf-interaction-hover-bg),
51
+ var(--surf-interaction-hover-bg)
52
+ );
50
53
  border-color: var(--surf-control-hover-border);
51
54
  }
52
55
  .surf-input:focus,
@@ -60,14 +63,14 @@
60
63
  .surf-select-trigger:disabled {
61
64
  opacity: var(--surf-control-disabled-opacity);
62
65
  cursor: not-allowed;
63
- background: var(--surf-control-disabled-background);
66
+ background: var(--surf-recessed);
64
67
  }
65
- /* Read-only shows a value, not a box to type in: the value sits on the surface
66
- plane without the field's border. Its text stays at full ink, apart from a
67
- disabled field, and focus still rings it, so the value stays copyable. */
68
+ /* Read-only shows a value, not a box to type in: the value sits on the recessed
69
+ step of its plane without the field's border. Its text stays at full ink, apart
70
+ from a disabled field, and focus still rings it, so the value stays copyable. */
68
71
  .surf-input[readonly]:not(:focus, [aria-invalid='true']) {
69
72
  border-color: transparent;
70
- background: var(--surf-surface);
73
+ background: var(--surf-recessed);
71
74
  }
72
75
  /* An invalid field keeps its edge while hovered; hover still tints the fill. */
73
76
  .surf-input[aria-invalid='true'],
@@ -26,11 +26,18 @@
26
26
  padding-inline: var(--surf-file-inset);
27
27
  border: 1px solid transparent;
28
28
  border-radius: var(--surf-radius-lg);
29
- background: var(--surf-surface);
29
+ background: var(--surf-recessed);
30
30
  transition: background-color var(--surf-duration-fast) var(--surf-ease-out);
31
+ /* The file list sits on the drop zone: its progress tracks take the other step. */
32
+ --surf-file-plane: var(--surf-recessed);
33
+ --surf-file-recessed: var(--surf-plane);
34
+ }
35
+ .surf-file-dropzone > * {
36
+ --surf-plane: var(--surf-file-plane);
37
+ --surf-recessed: var(--surf-file-recessed);
31
38
  }
32
39
  .surf-file-upload[data-dragging] .surf-file-dropzone {
33
- background: var(--surf-selected);
40
+ background-color: var(--surf-selected);
34
41
  }
35
42
  .surf-file-picker {
36
43
  display: flex;
@@ -43,9 +50,6 @@
43
50
  flex-direction: row;
44
51
  width: fit-content;
45
52
  cursor: pointer;
46
- font-size: var(--surf-type-body-size);
47
- letter-spacing: var(--surf-type-body-tracking);
48
- line-height: var(--surf-type-body-leading);
49
53
  }
50
54
  .surf-file-trigger[data-disabled] {
51
55
  opacity: var(--surf-control-disabled-opacity);
@@ -32,7 +32,8 @@
32
32
  flex-grow: 1;
33
33
  }
34
34
  }
35
- .surf-filter-bar-field > :is(.surf-control, .surf-select-trigger) {
35
+ /* Select renders its trigger inside a .surf-select wrapper. */
36
+ .surf-filter-bar-field > :is(.surf-control, .surf-select) {
36
37
  inline-size: 100%;
37
38
  min-inline-size: 0;
38
39
  }
@@ -51,6 +52,28 @@
51
52
  }
52
53
  }
53
54
  @layer surflane.sizes {
55
+ /* Compact density: fields sit 8px apart, and a phone puts two filters in a row;
56
+ the search field keeps a row of its own. */
57
+ :where([data-ui-density='compact'], [data-ui-density='compact'] *):is(
58
+ .surf-filter-bar,
59
+ .surf-filter-bar-controls
60
+ ) {
61
+ gap: var(--surf-ui-density-inset);
62
+ }
63
+ @media (max-width: 760px) {
64
+ :where(
65
+ [data-ui-density='compact'],
66
+ [data-ui-density='compact'] *
67
+ ).surf-filter-bar-field:not([data-width='search']) {
68
+ flex: 1 1 calc(50% - var(--surf-ui-density-inset) / 2);
69
+ }
70
+ :where(
71
+ [data-ui-density='compact'],
72
+ [data-ui-density='compact'] *
73
+ ).surf-filter-bar-field[data-width='search'] {
74
+ flex-basis: 100%;
75
+ }
76
+ }
54
77
  .surf-filter-bar-actions > .surf-button {
55
78
  max-inline-size: 100%;
56
79
  min-height: var(--surf-control-height);
@@ -25,7 +25,7 @@
25
25
  height: var(--surf-space-2);
26
26
  overflow: hidden;
27
27
  border-radius: var(--surf-radius-full);
28
- background: var(--surf-surface);
28
+ background: var(--surf-recessed);
29
29
  }
30
30
  .surf-gauge-indicator {
31
31
  background: var(--surf-ink-body);
@@ -35,7 +35,7 @@
35
35
  .surf-gauge-indicator[data-indeterminate] {
36
36
  width: 40%;
37
37
  height: inherit;
38
- animation: surf-progress-pulse 1.4s ease-in-out infinite;
38
+ animation: surf-progress-pulse var(--surf-motion-pulse) ease-in-out infinite;
39
39
  }
40
40
  @media (prefers-reduced-motion: reduce) {
41
41
  .surf-gauge-indicator,
@@ -21,11 +21,6 @@
21
21
  width: var(--surf-size-icon-large);
22
22
  height: var(--surf-size-icon-large);
23
23
  }
24
- /* Deprecated: the 24px size, off the scale. Removed in a later minor. */
25
- .surf-icon[data-size='lg'] {
26
- width: var(--surf-icon-size-lg);
27
- height: var(--surf-icon-size-lg);
28
- }
29
24
  .surf-icon[data-directional='true']:dir(rtl) {
30
25
  transform: scaleX(-1);
31
26
  }
@@ -19,19 +19,25 @@
19
19
  border-color var(--surf-duration-fast) var(--surf-ease-out),
20
20
  box-shadow var(--surf-duration-fast) var(--surf-ease-out);
21
21
  }
22
- .surf-combobox-group:hover,
23
- .surf-number-group:hover {
22
+ /* Hover as text fields have it: not while disabled or focused. */
23
+ :is(.surf-combobox-group, .surf-number-group):hover:not(
24
+ [data-disabled],
25
+ :focus-within
26
+ ) {
24
27
  border-color: var(--surf-control-hover-border);
25
- background: var(--surf-control-hover-background);
28
+ background-image: linear-gradient(
29
+ var(--surf-interaction-hover-bg),
30
+ var(--surf-interaction-hover-bg)
31
+ );
26
32
  }
27
- /* Read-only shows a value on the surface plane, without the field's border, as
33
+ /* Read-only shows a value on the recessed step, without the field's border, as
28
34
  text fields do. */
29
35
  :is(.surf-combobox-group, .surf-number-group)[data-readonly]:not(
30
36
  :focus-within,
31
37
  [data-invalid]
32
38
  ) {
33
39
  border-color: transparent;
34
- background: var(--surf-surface);
40
+ background: var(--surf-recessed);
35
41
  }
36
42
  .surf-combobox-group:focus-within,
37
43
  .surf-number-group:focus-within {
@@ -66,10 +72,19 @@
66
72
  opacity: var(--surf-control-disabled-opacity);
67
73
  cursor: not-allowed;
68
74
  }
69
- .surf-combobox-action[data-disabled],
70
- .surf-number-step[data-disabled],
71
- .surf-combobox-chip-remove[data-disabled] {
75
+ /* A button fades on its own only: in a disabled field the group faded it already. */
76
+ :is(
77
+ .surf-combobox-action,
78
+ .surf-number-step,
79
+ .surf-combobox-chip-remove
80
+ )[data-disabled]:not([data-disabled] *) {
72
81
  opacity: var(--surf-control-disabled-opacity);
82
+ }
83
+ :is(
84
+ .surf-combobox-action,
85
+ .surf-number-step,
86
+ .surf-combobox-chip-remove
87
+ )[data-disabled] {
73
88
  cursor: not-allowed;
74
89
  }
75
90
  /* A read-only field keeps its value readable and copyable, but its steppers do
@@ -1,9 +1,3 @@
1
1
  /* Link, 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
- :where([data-ui-size], [data-ui-size] *):is(.surf-link) {
7
- font-size: var(--surf-ui-font);
8
- }
9
- }