@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,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,20 +163,44 @@
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
158
184
  ) {
159
185
  padding-inline: var(--surf-ui-card-padding);
160
186
  }
187
+ /* Head and footer follow the size's card padding: 24/20px and 20px by default. */
161
188
  :where([data-ui-size], [data-ui-size] *).surf-dialog-header {
189
+ padding-block: var(--surf-ui-card-padding)
190
+ calc(
191
+ var(--surf-ui-card-padding) - var(--surf-space-1) -
192
+ var(--surf-dialog-ring-room)
193
+ );
162
194
  padding-inline-end: calc(
163
195
  var(--surf-control-height) + var(--surf-space-4) * 2
164
196
  );
165
197
  }
198
+ :where([data-ui-size], [data-ui-size] *).surf-dialog-footer {
199
+ padding-block: calc(var(--surf-ui-card-padding) - var(--surf-space-1));
200
+ }
201
+ :where([data-ui-size], [data-ui-size] *).surf-dialog-title {
202
+ font-size: var(--surf-ui-title-size);
203
+ line-height: var(--surf-ui-title-leading);
204
+ letter-spacing: var(--surf-ui-title-tracking);
205
+ }
166
206
  }
@@ -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);
@@ -15,17 +15,25 @@
15
15
  gap: var(--surf-space-4);
16
16
  min-inline-size: 0;
17
17
  }
18
+ /* A filter keeps its width; only the search field takes the room left in the row. */
18
19
  .surf-filter-bar-field {
19
20
  display: grid;
20
21
  gap: var(--surf-space-2);
21
- flex: 1 1 12rem;
22
+ flex: 0 1 12rem;
22
23
  min-inline-size: 0;
23
24
  max-inline-size: 100%;
24
25
  }
25
26
  .surf-filter-bar-field[data-width='search'] {
26
27
  flex: 2 1 20rem;
27
28
  }
28
- .surf-filter-bar-field > :is(.surf-control, .surf-select-trigger) {
29
+ /* On a phone each field fills its row. */
30
+ @media (max-width: 760px) {
31
+ .surf-filter-bar-field {
32
+ flex-grow: 1;
33
+ }
34
+ }
35
+ /* Select renders its trigger inside a .surf-select wrapper. */
36
+ .surf-filter-bar-field > :is(.surf-control, .surf-select) {
29
37
  inline-size: 100%;
30
38
  min-inline-size: 0;
31
39
  }
@@ -44,6 +52,28 @@
44
52
  }
45
53
  }
46
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
+ }
47
77
  .surf-filter-bar-actions > .surf-button {
48
78
  max-inline-size: 100%;
49
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
- }
@@ -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
+ }