@thalassic/themes 22.16.1 → 22.17.1

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 (35) hide show
  1. package/package.json +1 -1
  2. package/themes/thalassic/components/_odometer.scss +1 -14
  3. package/themes/thalassic/components/_table.scss +29 -2
  4. package/themes/thalassic/components/_virtual-scroll.scss +11 -0
  5. package/themes/thalassic/components/autocomplete/_autocomplete-panel.scss +21 -0
  6. package/themes/thalassic/components/calendar/_agenda.scss +3 -2
  7. package/themes/thalassic/components/calendar/_calendar.scss +27 -0
  8. package/themes/thalassic/components/calendar/_compact.scss +21 -0
  9. package/themes/thalassic/components/calendar/_lines.scss +23 -0
  10. package/themes/thalassic/components/calendar/_month-view.scss +132 -6
  11. package/themes/thalassic/components/calendar/_time-grid.scss +6 -5
  12. package/themes/thalassic/components/dialog/_dialog.scss +43 -0
  13. package/themes/thalassic/components/drawer/_drawer-side.scss +65 -0
  14. package/themes/thalassic/components/drawer/_drawer.scss +16 -0
  15. package/themes/thalassic/components/index.scss +3 -0
  16. package/themes/thalassic/components/multi-select/_multi-select-panel.scss +21 -0
  17. package/themes/thalassic/components/pagination/_pagination.scss +1 -1
  18. package/themes/thalassic/components/progress/_index.scss +3 -0
  19. package/themes/thalassic/components/progress/_progress-color.scss +5 -0
  20. package/themes/thalassic/components/progress/_progress-size.scss +13 -0
  21. package/themes/thalassic/components/progress/_progress.scss +38 -0
  22. package/themes/thalassic/components/rating/_index.scss +3 -0
  23. package/themes/thalassic/components/rating/_rating-color.scss +43 -0
  24. package/themes/thalassic/components/rating/_rating-size.scss +13 -0
  25. package/themes/thalassic/components/rating/_rating.scss +144 -0
  26. package/themes/thalassic/components/select/_select-panel.scss +21 -0
  27. package/themes/thalassic/components/stepper/_index.scss +1 -0
  28. package/themes/thalassic/components/stepper/_stepper-color.scss +34 -152
  29. package/themes/thalassic/components/stepper/_stepper-label-position.scss +41 -0
  30. package/themes/thalassic/components/stepper/_stepper-orientation.scss +13 -1
  31. package/themes/thalassic/components/stepper/_stepper.scss +37 -11
  32. package/themes/thalassic/components/toast/_toast.scss +4 -3
  33. package/themes/thalassic/mixins/_index.scss +2 -0
  34. package/themes/thalassic/mixins/_pop.scss +26 -0
  35. package/themes/thalassic/mixins/_visually-hidden.scss +19 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thalassic/themes",
3
- "version": "22.16.1",
3
+ "version": "22.17.1",
4
4
  "license": "MIT",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^22.0.0",
@@ -13,20 +13,7 @@
13
13
  line-height: 1;
14
14
 
15
15
  &__value {
16
- position: absolute;
17
-
18
- width: 1px;
19
- height: 1px;
20
-
21
- margin: -1px;
22
- padding: 0;
23
-
24
- overflow: hidden;
25
-
26
- clip-path: inset(50%);
27
- white-space: nowrap;
28
-
29
- border: 0;
16
+ @include mixins.visually-hidden;
30
17
  }
31
18
 
32
19
  &__row {
@@ -48,10 +48,14 @@
48
48
  &__header-cell {
49
49
  padding: var(--tls-table-cell-padding-y) var(--tls-table-cell-padding-x);
50
50
 
51
- color: var(--tls-table-header-color);
52
-
53
51
  border-bottom: 1px solid var(--tls-table-border-color);
54
52
 
53
+ // On the cell, not only the row: a sticky header cell must stay opaque on its own,
54
+ // since the row element does not stick with it.
55
+ background: var(--tls-table-header-background);
56
+
57
+ color: var(--tls-table-header-color);
58
+
55
59
  text-align: start;
56
60
  font-size: var(--font-label-small-size);
57
61
  font-weight: var(--tls-table-header-weight);
@@ -76,3 +80,26 @@
76
80
  background-color: var(--tls-table-background);
77
81
  }
78
82
  }
83
+
84
+ // Scrollable table: the component host bounds the height and scrolls the rows while
85
+ // the header row sticks to its top edge.
86
+ .tls-table-container--scrollable {
87
+ border-radius: var(--radius-md);
88
+
89
+ // Collapsed borders belong to the table grid, which does not travel with sticky
90
+ // cells — a pinned header would shed its border while rows scroll beneath it.
91
+ // Separate borders belong to the cells and stick with them. And sticky positioning
92
+ // confines itself to the nearest ancestor that clips overflow, so the table must
93
+ // not clip here — the scrollable container clips the corners instead.
94
+ .tls-table {
95
+ border-collapse: separate;
96
+ border-spacing: 0;
97
+ overflow: visible;
98
+ }
99
+
100
+ // Row borders move onto the cells: `tr` borders only paint under the collapsed
101
+ // model that sticky positioning rules out.
102
+ .tls-table__row:not(:last-child) .tls-table__cell {
103
+ border-bottom: 1px solid var(--tls-table-border-color);
104
+ }
105
+ }
@@ -0,0 +1,11 @@
1
+ @use '../mixins' as mixins;
2
+
3
+ .tls-virtual-scroll__viewport {
4
+ // Items are swapped in and out mid-gesture, so a scroll chain into an ancestor would
5
+ // move the page under content the reader is still tracking; keep the gesture here.
6
+ overscroll-behavior: contain;
7
+
8
+ &:focus-visible {
9
+ @include mixins.focus-ring;
10
+ }
11
+ }
@@ -23,6 +23,27 @@
23
23
  list-style: none;
24
24
 
25
25
  @include mixins.panel-open;
26
+
27
+ &--virtual {
28
+ // The virtual-scroll viewport inside is the scroll container; the panel only clips
29
+ // to its rounded corners.
30
+ overflow: hidden;
31
+
32
+ // Windowing positions every row at a fixed offset, so each row must occupy exactly
33
+ // the item size the viewport publishes — a taller custom option template is clipped
34
+ // rather than allowed to drift the rows out of place.
35
+ .tls-autocomplete__option {
36
+ height: var(--tls-virtual-scroll-item-size);
37
+
38
+ box-sizing: border-box;
39
+
40
+ overflow: hidden;
41
+ }
42
+
43
+ .tls-autocomplete__option-button {
44
+ height: 100%;
45
+ }
46
+ }
26
47
  }
27
48
 
28
49
  .tls-autocomplete__option {
@@ -1,4 +1,5 @@
1
1
  @use '../../mixins' as mixins;
2
+ @use 'lines';
2
3
 
3
4
  .tls-calendar-agenda-view {
4
5
  display: flex;
@@ -6,13 +7,13 @@
6
7
 
7
8
  &__day {
8
9
  display: grid;
9
- grid-template-columns: var(--spacing-24) 1fr;
10
+ grid-template-columns: 96px 1fr;
10
11
  gap: var(--spacing-4);
11
12
 
12
13
  padding-block: var(--spacing-3);
13
14
 
14
15
  & + & {
15
- border-block-start: 1px solid var(--color-outline-variant);
16
+ border-block-start: lines.horizontal();
16
17
  }
17
18
  }
18
19
 
@@ -1,4 +1,9 @@
1
+ @use 'compact';
2
+
1
3
  .tls-calendar {
4
+ // Named container so the dense layout can resolve against the calendar's own width rather
5
+ // than the viewport's, keeping a calendar in a narrow column compact on a wide screen.
6
+ container: tls-calendar / inline-size;
2
7
  display: flex;
3
8
  flex-direction: column;
4
9
  gap: var(--spacing-4);
@@ -9,6 +14,10 @@
9
14
  align-items: center;
10
15
  justify-content: space-between;
11
16
  gap: var(--spacing-4);
17
+
18
+ @include compact.compact {
19
+ gap: var(--spacing-3);
20
+ }
12
21
  }
13
22
 
14
23
  &__nav {
@@ -31,9 +40,27 @@
31
40
  font-weight: var(--font-weight-semibold);
32
41
  line-height: var(--font-title-large-line-height);
33
42
  color: var(--color-on-surface);
43
+
44
+ // A full-width basis ahead of its siblings puts the title on its own line, leaving the
45
+ // navigation and the switcher to share the one below.
46
+ @include compact.compact {
47
+ order: -1;
48
+ flex-basis: 100%;
49
+
50
+ font-size: var(--font-title-medium-size);
51
+ line-height: var(--font-title-medium-line-height);
52
+ }
34
53
  }
35
54
 
36
55
  &__views {
37
56
  flex-shrink: 0;
57
+
58
+ // The switcher scrolls rather than forcing a third row when its labels outgrow the space
59
+ // left by the navigation.
60
+ @include compact.compact {
61
+ min-width: 0;
62
+
63
+ overflow-x: auto;
64
+ }
38
65
  }
39
66
  }
@@ -0,0 +1,21 @@
1
+ // Width at which the calendar switches to its dense layout: the `sm` breakpoint. The value is
2
+ // restated rather than shared because this theme carries no Sass dependency on the library that
3
+ // owns the scale, so it must stay in sync with `sm` in `libs/core/src/styles/tools/_breakpoint.scss`
4
+ // — the same coupling `ViewportConfig` documents on the TypeScript side. A container query cannot
5
+ // read a custom property in its condition either, so it has to be a Sass value regardless.
6
+ $compact-width: 640px;
7
+
8
+ // Emits its content for both routes into the dense layout: forced on by the modifier class, and
9
+ // resolved automatically once the calendar's own width crosses the threshold. Rules inside must
10
+ // target a descendant of the calendar root, because a query container cannot match its own size.
11
+ @mixin compact {
12
+ .tls-calendar--compact & {
13
+ @content;
14
+ }
15
+
16
+ @container tls-calendar (max-width: #{$compact-width}) {
17
+ .tls-calendar--compact-auto & {
18
+ @content;
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,23 @@
1
+ // Every grid line in the calendar is read through these helpers rather than declared inline, so
2
+ // one declaration retints or removes a whole family of them across all views — for example
3
+ // `--tls-calendar-line-vertical: none` to drop every column separator.
4
+ //
5
+ // The tokens are deliberately left undeclared: each helper carries the default as a fallback, so
6
+ // a view still draws its lines when rendered on its own, and a token set anywhere above a view
7
+ // inherits down to it instead of being shadowed by a declaration on the view itself.
8
+ $default: 1px solid var(--color-outline-variant);
9
+
10
+ // Lines dividing rows: week rows in the month grid, hour rows in the time grid, days in the agenda.
11
+ @function horizontal() {
12
+ @return var(--tls-calendar-line-horizontal, #{$default});
13
+ }
14
+
15
+ // Lines dividing columns: day columns in the month grid and the time grid.
16
+ @function vertical() {
17
+ @return var(--tls-calendar-line-vertical, #{$default});
18
+ }
19
+
20
+ // The border enclosing a view's grid.
21
+ @function outer() {
22
+ @return var(--tls-calendar-line-outer, #{$default});
23
+ }
@@ -1,4 +1,6 @@
1
1
  @use '../../mixins' as mixins;
2
+ @use 'compact';
3
+ @use 'lines';
2
4
 
3
5
  .tls-calendar-month-view {
4
6
  // Lane geometry shared by the cell height and the bar overlay. The day-number reservation
@@ -8,6 +10,10 @@
8
10
  --tls-calendar-bar-gap: var(--spacing-1);
9
11
  --tls-calendar-more-height: var(--spacing-5);
10
12
 
13
+ // Diameter of a dense-layout event dot, and the gap between two of them.
14
+ --tls-calendar-marker-size: 6px;
15
+ --tls-calendar-marker-gap: var(--spacing-1);
16
+
11
17
  display: flex;
12
18
  flex-direction: column;
13
19
  gap: var(--spacing-2);
@@ -39,10 +45,15 @@
39
45
  1fr
40
46
  );
41
47
 
42
- // Collapsed borders: the grid draws the top/start edge, each cell the bottom/end edge,
43
- // so shared borders never double up and there are no gaps between cells.
44
- border-block-start: 1px solid var(--color-outline-variant);
45
- border-inline-start: 1px solid var(--color-outline-variant);
48
+ // The grid owns the outer edge and each cell only its interior lines, so the two families
49
+ // stay independently controllable and never double up along the perimeter.
50
+ border: lines.outer();
51
+
52
+ // With no bars to make room for, a row only has to hold a number and its dots. The floor
53
+ // doubles as the day cell's touch target.
54
+ @include compact.compact {
55
+ grid-auto-rows: minmax(var(--spacing-11), 1fr);
56
+ }
46
57
  }
47
58
 
48
59
  &__week {
@@ -50,6 +61,11 @@
50
61
 
51
62
  display: grid;
52
63
  grid-template-columns: repeat(7, 1fr);
64
+
65
+ // The grid's own border already closes the bottom of the last week.
66
+ &:last-child .tls-calendar-month-view__day {
67
+ border-block-end: none;
68
+ }
53
69
  }
54
70
 
55
71
  &__day {
@@ -60,8 +76,8 @@
60
76
  display: flex;
61
77
  flex-direction: column;
62
78
 
63
- border-block-end: 1px solid var(--color-outline-variant);
64
- border-inline-end: 1px solid var(--color-outline-variant);
79
+ border-block-end: lines.horizontal();
80
+ border-inline-end: lines.vertical();
65
81
 
66
82
  background-color: var(--color-surface-container-lowest);
67
83
 
@@ -73,6 +89,19 @@
73
89
  transition: background-color var(--motion-fast) var(--motion-ease-in-out);
74
90
  }
75
91
 
92
+ @include compact.compact {
93
+ padding-inline: var(--spacing-1);
94
+
95
+ // A grid rather than the default column flex: the dot row is reserved whether or not the
96
+ // day has events, so day numbers sit on one line across a week instead of shifting up
97
+ // wherever dots appear. `align-content` then centres the pair as a block.
98
+ display: grid;
99
+ grid-template-rows: auto var(--tls-calendar-marker-size);
100
+ align-content: center;
101
+ justify-items: center;
102
+ gap: var(--spacing-1);
103
+ }
104
+
76
105
  &:hover {
77
106
  background-color: var(--color-surface-container-low);
78
107
  }
@@ -81,6 +110,12 @@
81
110
  @include mixins.focus-ring;
82
111
  }
83
112
 
113
+ // Seven day cells precede the bar overlay in a week, so this is the last column — its line
114
+ // would otherwise land on top of the grid's own border.
115
+ &:nth-child(7) {
116
+ border-inline-end: none;
117
+ }
118
+
84
119
  &--outside {
85
120
  background-color: var(--color-surface-container);
86
121
 
@@ -110,13 +145,81 @@
110
145
 
111
146
  font-size: var(--font-label-small-size);
112
147
  font-weight: var(--font-weight-medium);
148
+
149
+ @include compact.compact {
150
+ align-self: center;
151
+ }
152
+ }
153
+
154
+ // Dots summarising a day's events, shown only where the bars are suppressed.
155
+ &__markers {
156
+ display: none;
157
+ justify-content: center;
158
+ gap: var(--tls-calendar-marker-gap);
159
+
160
+ @include compact.compact {
161
+ display: flex;
162
+ }
163
+ }
164
+
165
+ &__markers-label {
166
+ @include mixins.visually-hidden;
167
+ }
168
+
169
+ &__marker {
170
+ width: var(--tls-calendar-marker-size);
171
+ height: var(--tls-calendar-marker-size);
172
+
173
+ flex-shrink: 0;
174
+
175
+ border-radius: 50%;
176
+
177
+ background-color: var(--tls-calendar-marker-color, var(--color-primary));
178
+
179
+ &--primary {
180
+ --tls-calendar-marker-color: var(--color-primary);
181
+ }
182
+
183
+ &--secondary {
184
+ --tls-calendar-marker-color: var(--color-secondary);
185
+ }
186
+
187
+ &--tertiary {
188
+ --tls-calendar-marker-color: var(--color-tertiary);
189
+ }
190
+
191
+ &--success {
192
+ --tls-calendar-marker-color: var(--color-success);
193
+ }
194
+
195
+ &--info {
196
+ --tls-calendar-marker-color: var(--color-info);
197
+ }
198
+
199
+ &--warning {
200
+ --tls-calendar-marker-color: var(--color-warning);
201
+ }
202
+
203
+ &--danger {
204
+ --tls-calendar-marker-color: var(--color-danger);
205
+ }
113
206
  }
114
207
 
115
208
  &__more {
116
209
  margin-block-start: auto;
117
210
 
118
211
  font-size: var(--font-label-small-size);
212
+ // The row height reserves a single line for this label, so it truncates rather than wraps
213
+ // into space the grid has not accounted for.
214
+ white-space: nowrap;
215
+ overflow: hidden;
216
+ text-overflow: ellipsis;
119
217
  color: var(--color-on-surface-variant);
218
+
219
+ // The dots already stand for every event, counted ones included.
220
+ @include compact.compact {
221
+ display: none;
222
+ }
120
223
  }
121
224
 
122
225
  &__bars {
@@ -135,6 +238,12 @@
135
238
  padding-inline: var(--tls-calendar-bar-gap);
136
239
 
137
240
  pointer-events: none;
241
+
242
+ // The dense layout represents events as dots inside each cell instead. Selecting a single
243
+ // event is no longer reachable here, so a day cell's `dateSelect` is the way in.
244
+ @include compact.compact {
245
+ display: none;
246
+ }
138
247
  }
139
248
 
140
249
  &__bar {
@@ -152,4 +261,21 @@
152
261
  border-end-end-radius: 0;
153
262
  }
154
263
  }
264
+
265
+ // Square cells take their height from the column width, so the row can no longer carry a
266
+ // floor sized to the events it holds — a cell that overflows is clipped rather than growing.
267
+ // Declared after the dense layout's own row sizing so the ratio wins where both apply, which
268
+ // also means a square cell can fall under the dense layout's touch-target floor once the
269
+ // column is narrower than it.
270
+ &--square {
271
+ .tls-calendar-month-view__grid {
272
+ grid-auto-rows: auto;
273
+ }
274
+
275
+ .tls-calendar-month-view__day {
276
+ aspect-ratio: 1;
277
+
278
+ overflow: hidden;
279
+ }
280
+ }
155
281
  }
@@ -1,4 +1,5 @@
1
1
  @use '../../mixins' as mixins;
2
+ @use 'lines';
2
3
 
3
4
  .tls-calendar-time-grid {
4
5
  --tls-calendar-gutter: var(--spacing-16);
@@ -13,7 +14,7 @@
13
14
  }
14
15
 
15
16
  &__header {
16
- border-block-end: 1px solid var(--color-outline-variant);
17
+ border-block-end: lines.horizontal();
17
18
  }
18
19
 
19
20
  &__heading {
@@ -42,7 +43,7 @@
42
43
  }
43
44
 
44
45
  &__all-day {
45
- border-block-end: 1px solid var(--color-outline-variant);
46
+ border-block-end: lines.horizontal();
46
47
  }
47
48
 
48
49
  &__gutter-label {
@@ -60,7 +61,7 @@
60
61
  flex-direction: column;
61
62
  gap: var(--spacing-1);
62
63
 
63
- border-inline-start: 1px solid var(--color-outline-variant);
64
+ border-inline-start: lines.vertical();
64
65
  }
65
66
 
66
67
  &__gutter {
@@ -86,7 +87,7 @@
86
87
  &__column {
87
88
  position: relative;
88
89
 
89
- border-inline-start: 1px solid var(--color-outline-variant);
90
+ border-inline-start: lines.vertical();
90
91
 
91
92
  &--today {
92
93
  background-color: var(--color-surface-container-low);
@@ -99,7 +100,7 @@
99
100
  box-sizing: border-box;
100
101
  height: var(--tls-calendar-hour-height);
101
102
 
102
- border-block-start: 1px solid var(--color-outline-variant);
103
+ border-block-start: lines.horizontal();
103
104
  }
104
105
 
105
106
  &__event {
@@ -1,3 +1,29 @@
1
+ @use '../../mixins' as mixins;
2
+
3
+ @keyframes tls-dialog-open {
4
+ from {
5
+ opacity: 0;
6
+ transform: scale(0.96);
7
+ }
8
+
9
+ to {
10
+ opacity: 1;
11
+ transform: none;
12
+ }
13
+ }
14
+
15
+ @keyframes tls-dialog-close {
16
+ from {
17
+ opacity: 1;
18
+ transform: none;
19
+ }
20
+
21
+ to {
22
+ opacity: 0;
23
+ transform: scale(0.96);
24
+ }
25
+ }
26
+
1
27
  .tls-dialog-backdrop {
2
28
  --tls-dialog-backdrop: rgb(0 0 0 / 0.4);
3
29
 
@@ -33,6 +59,23 @@
33
59
  color: var(--tls-dialog-color);
34
60
  box-shadow: var(--tls-dialog-shadow);
35
61
 
62
+ // The fade-and-scale communicates the surface appearing and dismissing, so it
63
+ // is a functional cue on the `essential` tier and survives reduced motion
64
+ // (resolved by `MotionService`). Enter and leave use distinct keyframe names,
65
+ // not one shared keyframe with `reverse`: the container keeps its `--enter`
66
+ // class for the panel's whole lifetime, so swapping to `--leave` must change
67
+ // `animation-name` to force the browser to start a fresh animation (a mere
68
+ // `animation-direction` change does not restart an already-finished animation).
69
+ @include mixins.motion(essential) {
70
+ &--enter {
71
+ animation: tls-dialog-open var(--motion-instant) var(--motion-ease-out) both;
72
+ }
73
+
74
+ &--leave {
75
+ animation: tls-dialog-close var(--motion-instant) var(--motion-ease-in) both;
76
+ }
77
+ }
78
+
36
79
  // The portal inserts the consumer's content component as the dialog's only
37
80
  // content child, so that host — not the header/body/footer inside it — is
38
81
  // this flex column's item. Make it a flex column in turn, or the body cannot
@@ -27,20 +27,85 @@
27
27
  max-height: 100dvh;
28
28
  }
29
29
 
30
+ // Sized by content along the main axis, capped so a short panel hugs its content
31
+ // while a tall one still scrolls instead of filling the viewport.
32
+ &--auto {
33
+ &.tls-drawer--start,
34
+ &.tls-drawer--end {
35
+ width: fit-content;
36
+ max-width: 90dvw;
37
+ }
38
+
39
+ &.tls-drawer--top,
40
+ &.tls-drawer--bottom {
41
+ height: fit-content;
42
+ max-height: 90dvh;
43
+ }
44
+ }
45
+
46
+ // Each side pins the grabber to the panel's inner edge (the one facing the
47
+ // viewport), centered along the cross axis. Logical insets keep the side panels'
48
+ // placement correct in RTL, where the physical edges swap.
30
49
  &--start {
31
50
  --tls-drawer-offset: translateX(-100%);
51
+
52
+ .tls-drawer__grabber {
53
+ inset-inline-end: var(--tls-drawer-grabber-inset);
54
+ inset-block: 0;
55
+
56
+ width: var(--tls-drawer-grabber-thickness);
57
+ height: var(--tls-drawer-grabber-length);
58
+
59
+ margin-block: auto;
60
+ }
32
61
  }
33
62
 
34
63
  &--end {
35
64
  --tls-drawer-offset: translateX(100%);
65
+
66
+ .tls-drawer__grabber {
67
+ inset-inline-start: var(--tls-drawer-grabber-inset);
68
+ inset-block: 0;
69
+
70
+ width: var(--tls-drawer-grabber-thickness);
71
+ height: var(--tls-drawer-grabber-length);
72
+
73
+ margin-block: auto;
74
+ }
36
75
  }
37
76
 
38
77
  &--top {
39
78
  --tls-drawer-offset: translateY(-100%);
79
+
80
+ // Keeps the content clear of a device cutout on the pinned edge.
81
+ padding-top: env(safe-area-inset-top);
82
+
83
+ .tls-drawer__grabber {
84
+ bottom: var(--tls-drawer-grabber-inset);
85
+ inset-inline: 0;
86
+
87
+ width: var(--tls-drawer-grabber-length);
88
+ height: var(--tls-drawer-grabber-thickness);
89
+
90
+ margin-inline: auto;
91
+ }
40
92
  }
41
93
 
42
94
  &--bottom {
43
95
  --tls-drawer-offset: translateY(100%);
96
+
97
+ // Keeps the content clear of the home indicator on the pinned edge.
98
+ padding-bottom: env(safe-area-inset-bottom);
99
+
100
+ .tls-drawer__grabber {
101
+ top: var(--tls-drawer-grabber-inset);
102
+ inset-inline: 0;
103
+
104
+ width: var(--tls-drawer-grabber-length);
105
+ height: var(--tls-drawer-grabber-thickness);
106
+
107
+ margin-inline: auto;
108
+ }
44
109
  }
45
110
 
46
111
  // Rounds the inner edge only (the two corners facing the viewport). Opt-in; the
@@ -34,6 +34,9 @@
34
34
  --tls-drawer-padding: var(--spacing-6);
35
35
  --tls-drawer-close-size: 16px;
36
36
  --tls-drawer-close-padding: var(--spacing-4);
37
+ --tls-drawer-grabber-length: 36px;
38
+ --tls-drawer-grabber-thickness: 5px;
39
+ --tls-drawer-grabber-inset: var(--spacing-2);
37
40
 
38
41
  position: relative;
39
42
 
@@ -93,6 +96,17 @@
93
96
  }
94
97
  }
95
98
 
99
+ // Grabber pill, the visual affordance of a sheet-style panel. Placement along the
100
+ // panel's inner edge is side-dependent and lives with the side modifiers.
101
+ &__grabber {
102
+ position: absolute;
103
+ z-index: 1;
104
+
105
+ border-radius: calc(var(--tls-drawer-grabber-thickness) / 2);
106
+
107
+ background-color: var(--color-outline-variant);
108
+ }
109
+
96
110
  &__header {
97
111
  padding: var(--tls-drawer-padding) var(--tls-drawer-padding) 0;
98
112
  }
@@ -108,6 +122,8 @@
108
122
  flex: 1;
109
123
  padding: var(--spacing-4) var(--tls-drawer-padding);
110
124
  overflow-y: auto;
125
+ // Scrolling the drawer's content must not chain into scrolling the page behind it.
126
+ overscroll-behavior: contain;
111
127
  }
112
128
 
113
129
  &__footer {
@@ -41,7 +41,9 @@
41
41
  @forward 'input-number';
42
42
  @forward 'pin-input';
43
43
  @forward 'popover';
44
+ @forward 'progress';
44
45
  @forward 'radio-button';
46
+ @forward 'rating';
45
47
  @forward 'rich-text-editor';
46
48
  @forward 'skeleton';
47
49
  @forward 'slider';
@@ -49,3 +51,4 @@
49
51
  @forward 'table';
50
52
  @forward 'toggle-button';
51
53
  @forward 'toggle-group';
54
+ @forward 'virtual-scroll';