@thalassic/themes 22.8.0 → 22.10.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 (31) hide show
  1. package/package.json +1 -1
  2. package/themes/thalassic/components/autocomplete/_autocomplete-panel.scss +84 -0
  3. package/themes/thalassic/components/autocomplete/_autocomplete-size.scss +13 -0
  4. package/themes/thalassic/components/autocomplete/_autocomplete.scss +99 -0
  5. package/themes/thalassic/components/autocomplete/_index.scss +3 -0
  6. package/themes/thalassic/components/badge/_badge.scss +7 -0
  7. package/themes/thalassic/components/breadcrumbs/_breadcrumbs.scss +92 -0
  8. package/themes/thalassic/components/breadcrumbs/_index.scss +1 -0
  9. package/themes/thalassic/components/button/_button-color.scss +0 -1
  10. package/themes/thalassic/components/button/_button-variant.scss +14 -116
  11. package/themes/thalassic/components/button/_button.scss +13 -31
  12. package/themes/thalassic/components/chip/_chip-control.scss +4 -0
  13. package/themes/thalassic/components/form-control-group/_form-control-addon.scss +3 -0
  14. package/themes/thalassic/components/index.scss +4 -0
  15. package/themes/thalassic/components/kbd/_index.scss +2 -0
  16. package/themes/thalassic/components/kbd/_kbd-size.scss +19 -0
  17. package/themes/thalassic/components/kbd/_kbd.scss +24 -0
  18. package/themes/thalassic/components/multi-select/_multi-select.scss +4 -0
  19. package/themes/thalassic/components/pagination/_pagination.scss +10 -0
  20. package/themes/thalassic/components/select/_select.scss +4 -0
  21. package/themes/thalassic/components/switch/_switch.scss +4 -0
  22. package/themes/thalassic/components/toggle-button/_index.scss +5 -0
  23. package/themes/thalassic/components/toggle-button/_toggle-button-checked.scss +44 -0
  24. package/themes/thalassic/components/toggle-button/_toggle-button-color.scss +9 -0
  25. package/themes/thalassic/components/toggle-button/_toggle-button-size.scss +31 -0
  26. package/themes/thalassic/components/toggle-button/_toggle-button-variant.scss +21 -0
  27. package/themes/thalassic/components/toggle-button/_toggle-button.scss +79 -0
  28. package/themes/thalassic/mixins/_index.scss +2 -0
  29. package/themes/thalassic/mixins/_touch-target.scss +30 -0
  30. package/themes/thalassic/mixins/_variant-treatment.scss +97 -0
  31. package/themes/thalassic/semantic/_control.scss +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thalassic/themes",
3
- "version": "22.8.0",
3
+ "version": "22.10.0",
4
4
  "license": "MIT",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^22.0.0",
@@ -0,0 +1,84 @@
1
+ @use '../../mixins' as mixins;
2
+
3
+ .tls-autocomplete__panel {
4
+ --tls-autocomplete-panel-max-height: 240px;
5
+ --tls-autocomplete-panel-padding-block: var(--spacing-1);
6
+ --tls-autocomplete-panel-background: var(--color-surface-container-lowest);
7
+ --tls-autocomplete-panel-border-radius: var(--radius-md);
8
+ --tls-autocomplete-panel-border: 1px solid var(--color-outline-variant);
9
+ --tls-autocomplete-panel-shadow: var(--shadow-sm);
10
+
11
+ width: 100%;
12
+ max-height: var(--tls-autocomplete-panel-max-height);
13
+
14
+ margin: 0;
15
+ padding: var(--tls-autocomplete-panel-padding-block) 0;
16
+
17
+ background-color: var(--tls-autocomplete-panel-background);
18
+ border: var(--tls-autocomplete-panel-border);
19
+ border-radius: var(--tls-autocomplete-panel-border-radius);
20
+ box-shadow: var(--tls-autocomplete-panel-shadow);
21
+
22
+ overflow-y: auto;
23
+ list-style: none;
24
+
25
+ @include mixins.panel-open;
26
+ }
27
+
28
+ .tls-autocomplete__option {
29
+ --tls-autocomplete-option-color: var(--color-on-surface);
30
+ --tls-autocomplete-option-background: transparent;
31
+ --tls-autocomplete-option-padding-block: var(--spacing-2);
32
+ --tls-autocomplete-option-padding-inline: var(--spacing-3);
33
+
34
+ &.tls-autocomplete__option--active {
35
+ --tls-autocomplete-option-background: var(--color-surface-variant);
36
+ }
37
+
38
+ &.tls-autocomplete__option--selected {
39
+ --tls-autocomplete-option-color: var(--color-primary);
40
+ }
41
+
42
+ &.tls-autocomplete__option--disabled .tls-autocomplete__option-button {
43
+ opacity: 0.4;
44
+ cursor: not-allowed;
45
+ }
46
+
47
+ .tls-autocomplete__option-button {
48
+ width: 100%;
49
+ padding-block: var(--tls-autocomplete-option-padding-block);
50
+ padding-inline: var(--tls-autocomplete-option-padding-inline);
51
+
52
+ display: flex;
53
+ align-items: center;
54
+
55
+ appearance: none;
56
+ cursor: pointer;
57
+ text-align: start;
58
+
59
+ color: var(--tls-autocomplete-option-color);
60
+ background: var(--tls-autocomplete-option-background);
61
+ border: none;
62
+
63
+ font-size: var(--font-body-medium-size);
64
+ font-weight: var(--font-body-medium-weight);
65
+ line-height: var(--font-body-medium-line-height);
66
+ letter-spacing: var(--font-body-medium-letter-spacing);
67
+
68
+ &:focus-visible {
69
+ outline: none;
70
+ }
71
+ }
72
+ }
73
+
74
+ .tls-autocomplete__empty {
75
+ padding-block: var(--spacing-2);
76
+ padding-inline: var(--spacing-3);
77
+
78
+ color: var(--color-outline);
79
+
80
+ font-size: var(--font-body-medium-size);
81
+ font-weight: var(--font-body-medium-weight);
82
+ line-height: var(--font-body-medium-line-height);
83
+ letter-spacing: var(--font-body-medium-letter-spacing);
84
+ }
@@ -0,0 +1,13 @@
1
+ .tls-autocomplete {
2
+ &.tls-form-control--sm {
3
+ --tls-autocomplete-clear-size: 0.875rem;
4
+ }
5
+
6
+ &.tls-form-control--md {
7
+ --tls-autocomplete-clear-size: 1rem;
8
+ }
9
+
10
+ &.tls-form-control--lg {
11
+ --tls-autocomplete-clear-size: 1.125rem;
12
+ }
13
+ }
@@ -0,0 +1,99 @@
1
+ @use '../../mixins' as mixins;
2
+
3
+ .tls-autocomplete {
4
+ --tls-autocomplete-width: 200px;
5
+
6
+ position: relative;
7
+ display: inline-block;
8
+
9
+ &.tls-form-control {
10
+ --tls-form-control-width: var(--tls-autocomplete-width);
11
+
12
+ display: inline-flex;
13
+ align-items: stretch;
14
+ padding-inline: 0;
15
+
16
+ &.tls-form-control--fluid {
17
+ --tls-form-control-width: 100%;
18
+ }
19
+
20
+ &.tls-form-control--disabled {
21
+ cursor: not-allowed;
22
+ }
23
+
24
+ &:focus-within {
25
+ border: var(--tls-form-control-border-width) solid var(--color-primary);
26
+ box-shadow: 0 0 3px -1px var(--color-primary);
27
+ }
28
+
29
+ &.tls-form-control--show-error:not(.tls-form-control--disabled) .tls-autocomplete__trigger::placeholder {
30
+ color: var(--color-danger);
31
+ }
32
+
33
+ .tls-autocomplete__trigger {
34
+ flex: 1;
35
+ min-width: 0;
36
+
37
+ padding-inline: var(--tls-form-control-padding-inline);
38
+
39
+ appearance: none;
40
+ background: transparent;
41
+ border: none;
42
+ outline: none;
43
+
44
+ color: inherit;
45
+ font: inherit;
46
+ letter-spacing: inherit;
47
+
48
+ &::placeholder {
49
+ color: var(--color-outline);
50
+ }
51
+
52
+ &:disabled {
53
+ cursor: not-allowed;
54
+ }
55
+ }
56
+
57
+ // Reserve room at the trailing edge for the clear button or spinner.
58
+ &:has(.tls-autocomplete__clear) .tls-autocomplete__trigger,
59
+ &.tls-form-control--pending .tls-autocomplete__trigger {
60
+ padding-inline-end: calc(
61
+ var(--tls-form-control-padding-inline) * 2 + var(--tls-autocomplete-clear-size)
62
+ );
63
+ }
64
+
65
+ .tls-autocomplete__spinner {
66
+ position: absolute;
67
+ inset-block: 0;
68
+ inset-inline-end: var(--tls-form-control-padding-inline);
69
+
70
+ margin-block: auto;
71
+
72
+ pointer-events: none;
73
+ }
74
+
75
+ .tls-autocomplete__clear {
76
+ position: absolute;
77
+ top: 50%;
78
+ inset-inline-end: var(--tls-form-control-padding-inline);
79
+
80
+ display: flex;
81
+ align-items: center;
82
+
83
+ color: var(--color-outline);
84
+
85
+ transform: translateY(-50%);
86
+
87
+ @include mixins.touch-target(var(--tls-autocomplete-clear-size));
88
+
89
+ &:hover {
90
+ color: var(--color-on-surface);
91
+ }
92
+
93
+ .tls-autocomplete__clear-icon {
94
+ width: var(--tls-autocomplete-clear-size);
95
+ height: var(--tls-autocomplete-clear-size);
96
+ }
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,3 @@
1
+ @forward 'autocomplete';
2
+ @forward 'autocomplete-panel';
3
+ @forward 'autocomplete-size';
@@ -63,4 +63,11 @@
63
63
  &--inset &__indicator {
64
64
  --tls-badge-offset: 0px;
65
65
  }
66
+
67
+ &--bordered &__indicator {
68
+ --tls-badge-border-width: 2px;
69
+ --tls-badge-border-color: var(--color-surface);
70
+
71
+ box-shadow: 0 0 0 var(--tls-badge-border-width) var(--tls-badge-border-color);
72
+ }
66
73
  }
@@ -0,0 +1,92 @@
1
+ @use '../../mixins' as mixins;
2
+
3
+ .tls-breadcrumbs {
4
+ --tls-breadcrumbs-gap: var(--spacing-2);
5
+ --tls-breadcrumbs-color: var(--color-secondary);
6
+ --tls-breadcrumbs-current-color: var(--color-on-surface);
7
+ --tls-breadcrumbs-link-height: 24px;
8
+ --tls-breadcrumbs-icon-size: 16px;
9
+ --tls-breadcrumbs-separator-size: 14px;
10
+
11
+ display: block;
12
+
13
+ &__list {
14
+ display: flex;
15
+ flex-wrap: wrap;
16
+ align-items: center;
17
+ // The links' touch-target expansion overflows 10px past each edge; wrapped
18
+ // rows need a cross-axis gap of at least that overflow (see _touch-target.scss).
19
+ row-gap: var(--spacing-3);
20
+ column-gap: var(--tls-breadcrumbs-gap);
21
+ }
22
+
23
+ &__item {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--tls-breadcrumbs-gap);
27
+ }
28
+
29
+ &__link {
30
+ position: relative;
31
+
32
+ height: var(--tls-breadcrumbs-link-height);
33
+
34
+ display: flex;
35
+ align-items: center;
36
+ gap: var(--spacing-1);
37
+
38
+ text-decoration: none;
39
+ color: var(--tls-breadcrumbs-color);
40
+
41
+ cursor: pointer;
42
+
43
+ @include mixins.touch-target(var(--tls-breadcrumbs-link-height));
44
+
45
+ &:hover {
46
+ text-decoration: underline;
47
+ }
48
+
49
+ &[aria-current='page'] {
50
+ color: var(--tls-breadcrumbs-current-color);
51
+ }
52
+ }
53
+
54
+ &__text {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: var(--spacing-1);
58
+
59
+ color: var(--tls-breadcrumbs-color);
60
+
61
+ &[aria-current='page'] {
62
+ color: var(--tls-breadcrumbs-current-color);
63
+ }
64
+ }
65
+
66
+ &__item-icon {
67
+ width: var(--tls-breadcrumbs-icon-size);
68
+ height: var(--tls-breadcrumbs-icon-size);
69
+ }
70
+
71
+ &__separator {
72
+ width: var(--tls-breadcrumbs-separator-size);
73
+ height: var(--tls-breadcrumbs-separator-size);
74
+
75
+ display: flex;
76
+ justify-content: center;
77
+ align-items: center;
78
+
79
+ color: var(--tls-breadcrumbs-color);
80
+
81
+ .tls-icon {
82
+ width: 100%;
83
+ height: 100%;
84
+ }
85
+
86
+ // A directional separator glyph (chevron) must point toward the next item,
87
+ // which is the opposite physical direction in RTL.
88
+ :root[dir='rtl'] & {
89
+ transform: scaleX(-1);
90
+ }
91
+ }
92
+ }
@@ -0,0 +1 @@
1
+ @forward 'breadcrumbs';
@@ -2,7 +2,6 @@
2
2
 
3
3
  .tls-button {
4
4
  @include mixins.color-variants('tls-button', $tonal: true);
5
- @include mixins.tonal-swap('tls-button');
6
5
 
7
6
  &.tls-button--disabled {
8
7
  --tls-button-on-color: var(--color-on-surface-variant);
@@ -1,137 +1,35 @@
1
- .tls-button {
2
- @mixin hover-focus-state {
3
- &:not(.tls-button--disabled):hover,
4
- &:not(.tls-button--disabled):focus-visible {
5
- @content;
6
- }
7
- }
1
+ @use '../../mixins' as mixins;
8
2
 
9
- @mixin active-state {
10
- &:not(.tls-button--disabled):active {
11
- @content;
12
- }
3
+ .tls-button {
4
+ // Variants
5
+ &--filled {
6
+ @include mixins.variant-treatment(filled, 'tls-button');
13
7
  }
14
8
 
15
- // Variants
16
- &--filled,
17
9
  &--tonal {
18
- color: var(--tls-button-on-color);
19
-
20
- &::before {
21
- background-color: var(--tls-button-color);
22
- }
23
-
24
- @include hover-focus-state {
25
- &::before {
26
- opacity: 0.85;
27
- }
28
- }
29
-
30
- @include active-state {
31
- &::before {
32
- opacity: 0.75;
33
- }
34
- }
10
+ @include mixins.variant-treatment(tonal, 'tls-button');
35
11
  }
36
12
 
37
13
  &--outlined {
38
- color: var(--tls-button-color);
39
-
40
- &::before {
41
- background-color: var(--tls-button-color);
42
-
43
- opacity: 0;
44
- }
45
-
46
- &::after {
47
- border: var(--tls-button-border-width) solid var(--tls-button-color);
48
-
49
- transition: border-color var(--motion-fast) var(--motion-ease-in-out);
50
- }
51
-
52
- @include hover-focus-state {
53
- &::before {
54
- opacity: 0.075;
55
- }
56
- }
57
-
58
- @include active-state {
59
- &::before {
60
- opacity: 0.15;
61
- }
62
- }
14
+ @include mixins.variant-treatment(outlined, 'tls-button');
63
15
  }
64
16
 
65
17
  &--text {
66
- color: var(--tls-button-color);
67
-
68
- &::before {
69
- background-color: var(--tls-button-color);
70
-
71
- opacity: 0;
72
- }
73
-
74
- @include hover-focus-state {
75
- &::before {
76
- opacity: 0.075;
77
- }
78
- }
79
-
80
- @include active-state {
81
- &::before {
82
- opacity: 0.15;
83
- }
84
- }
18
+ @include mixins.variant-treatment(text, 'tls-button');
85
19
  }
86
20
 
87
21
  &--elevated {
88
- color: var(--tls-button-color);
89
-
90
- transition: box-shadow var(--motion-fast) var(--motion-ease-in-out);
91
-
92
- &::before {
93
- background-color: var(--tls-button-color);
94
-
95
- opacity: 0;
96
- }
97
-
98
- &::after {
99
- box-shadow: var(--shadow-sm);
100
- }
101
-
102
- @include hover-focus-state {
103
- &::before {
104
- opacity: 0.075;
105
- }
106
- }
107
-
108
- @include active-state {
109
- &::before {
110
- opacity: 0.15;
111
- }
112
-
113
- &::after {
114
- box-shadow: var(--shadow-xs);
115
- }
116
- }
22
+ @include mixins.variant-treatment(elevated, 'tls-button');
117
23
  }
118
24
 
119
25
  &--disabled {
120
- color: var(--tls-button-on-color);
121
-
122
- cursor: not-allowed;
123
-
124
- &::before {
125
- opacity: 1;
26
+ border-color: var(--color-outline);
126
27
 
127
- transition: none;
128
- }
28
+ background-color: var(--tls-button-color);
29
+ box-shadow: none;
129
30
 
130
- &::after {
131
- border: var(--tls-button-border-width) solid var(--color-outline);
132
- box-shadow: none;
31
+ color: var(--tls-button-on-color);
133
32
 
134
- transition: none;
135
- }
33
+ cursor: not-allowed;
136
34
  }
137
35
  }
@@ -7,48 +7,30 @@
7
7
  position: relative;
8
8
  z-index: 1;
9
9
 
10
+ box-sizing: border-box;
11
+
10
12
  display: inline-flex;
11
13
  justify-content: center;
12
14
  align-items: center;
13
15
  gap: 0.25rem;
14
16
 
15
- cursor: pointer;
16
- user-select: none;
17
-
17
+ border: var(--tls-button-border-width) solid transparent;
18
18
  border-radius: var(--tls-button-border-radius);
19
+
19
20
  background-color: var(--color-surface);
20
21
 
21
- transition: color var(--motion-fast) var(--motion-ease-in-out);
22
+ cursor: pointer;
23
+ user-select: none;
24
+
25
+ transition:
26
+ background-color var(--motion-fast) var(--motion-ease-in-out),
27
+ border-color var(--motion-fast) var(--motion-ease-in-out),
28
+ box-shadow var(--motion-fast) var(--motion-ease-in-out),
29
+ color var(--motion-fast) var(--motion-ease-in-out);
22
30
 
23
31
  &:focus-visible {
24
32
  @include mixins.focus-ring;
25
33
  }
26
34
 
27
- &::before,
28
- &::after {
29
- content: '';
30
- width: 100%;
31
- height: 100%;
32
-
33
- position: absolute;
34
- z-index: -1;
35
-
36
- border: var(--tls-button-border-width) solid var(--tls-button-color);
37
- border-radius: var(--tls-button-border-radius);
38
- }
39
-
40
- &::before {
41
- transition:
42
- background-color var(--motion-fast) var(--motion-ease-in-out),
43
- opacity var(--motion-fast) var(--motion-ease-in-out);
44
- }
45
-
46
- &::after {
47
- border: none;
48
- border-radius: var(--tls-button-border-radius);
49
-
50
- transition:
51
- border-color var(--motion-fast) var(--motion-ease-in-out),
52
- box-shadow var(--motion-fast) var(--motion-ease-in-out);
53
- }
35
+ @include mixins.touch-target(var(--tls-button-height));
54
36
  }
@@ -2,7 +2,11 @@
2
2
 
3
3
  .tls-chip {
4
4
  &.tls-chip--control {
5
+ position: relative;
6
+
5
7
  cursor: pointer;
8
+
9
+ @include mixins.touch-target(var(--tls-chip-height));
6
10
  }
7
11
 
8
12
  &.tls-chip--checked {
@@ -1,6 +1,9 @@
1
1
  @use '../../mixins' as mixins;
2
2
 
3
3
  .tls-form-control-addon {
4
+ // Addons are secondary to the control's value (icons, unit labels, toggles).
5
+ color: var(--color-on-surface-variant);
6
+
4
7
  &:focus-visible {
5
8
  @include mixins.focus-ring;
6
9
  border-radius: var(--radius-md);
@@ -1,5 +1,7 @@
1
1
  @forward 'alert';
2
+ @forward 'autocomplete';
2
3
  @forward 'badge';
4
+ @forward 'breadcrumbs';
3
5
  @forward 'button';
4
6
  @forward 'calendar';
5
7
  @forward 'chart';
@@ -7,6 +9,7 @@
7
9
  @forward 'date-time-picker';
8
10
  @forward 'dialog';
9
11
  @forward 'drawer';
12
+ @forward 'kbd';
10
13
  @forward 'loader';
11
14
  @forward 'pagination';
12
15
  @forward 'multi-select';
@@ -39,4 +42,5 @@
39
42
  @forward 'skeleton';
40
43
  @forward 'splitter';
41
44
  @forward 'table';
45
+ @forward 'toggle-button';
42
46
  @forward 'toggle-group';
@@ -0,0 +1,2 @@
1
+ @forward "kbd";
2
+ @forward "kbd-size";
@@ -0,0 +1,19 @@
1
+ .tls-kbd {
2
+ &.tls-kbd--sm {
3
+ --tls-kbd-size: 20px;
4
+ --tls-kbd-padding-inline: var(--spacing-1);
5
+ --tls-kbd-font-size: var(--font-label-small-size);
6
+ }
7
+
8
+ &.tls-kbd--md {
9
+ --tls-kbd-size: 24px;
10
+ --tls-kbd-padding-inline: var(--spacing-1);
11
+ --tls-kbd-font-size: var(--font-label-small-size);
12
+ }
13
+
14
+ &.tls-kbd--lg {
15
+ --tls-kbd-size: 28px;
16
+ --tls-kbd-padding-inline: var(--spacing-2);
17
+ --tls-kbd-font-size: var(--font-label-medium-size);
18
+ }
19
+ }
@@ -0,0 +1,24 @@
1
+ .tls-kbd {
2
+ min-width: var(--tls-kbd-size);
3
+ height: var(--tls-kbd-size);
4
+
5
+ padding-inline: var(--tls-kbd-padding-inline);
6
+
7
+ display: inline-flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+
11
+ border: 1px solid var(--color-outline-variant);
12
+ border-radius: var(--radius-sm);
13
+
14
+ background-color: var(--color-surface-container-low);
15
+ box-shadow: 0 1px 0 var(--color-outline-variant);
16
+
17
+ font-family: var(--font-family-mono);
18
+ font-size: var(--tls-kbd-font-size);
19
+ font-weight: var(--font-weight-medium);
20
+ line-height: 1;
21
+ text-box: trim-both cap alphabetic;
22
+ white-space: nowrap;
23
+ color: var(--color-on-surface-variant);
24
+ }
@@ -1,3 +1,5 @@
1
+ @use '../../mixins' as mixins;
2
+
1
3
  .tls-multi-select {
2
4
  --tls-multi-select-width: 200px;
3
5
  --tls-multi-select-gap: var(--spacing-2);
@@ -97,6 +99,8 @@
97
99
 
98
100
  transform: translateY(-50%);
99
101
 
102
+ @include mixins.touch-target(var(--tls-multi-select-clear-size));
103
+
100
104
  &:hover {
101
105
  color: var(--color-on-surface);
102
106
  }
@@ -1,3 +1,5 @@
1
+ @use '../../mixins' as mixins;
2
+
1
3
  .tls-pagination {
2
4
  --tls-pagination-gap: 16px;
3
5
  --tls-pagination-color: var(--color-secondary);
@@ -18,6 +20,8 @@
18
20
  }
19
21
 
20
22
  &__button {
23
+ position: relative;
24
+
21
25
  height: var(--tls-pagination-button-height);
22
26
  padding-inline: var(--tls-pagination-button-padding-inline);
23
27
 
@@ -31,6 +35,8 @@
31
35
 
32
36
  cursor: pointer;
33
37
 
38
+ @include mixins.touch-target(var(--tls-pagination-button-height));
39
+
34
40
  &:hover {
35
41
  outline: var(--tls-pagination-hover-outline);
36
42
  }
@@ -58,6 +64,8 @@
58
64
  }
59
65
 
60
66
  &__item-button {
67
+ position: relative;
68
+
61
69
  width: var(--tls-pagination-item-size);
62
70
  height: var(--tls-pagination-item-size);
63
71
 
@@ -72,6 +80,8 @@
72
80
 
73
81
  cursor: pointer;
74
82
 
83
+ @include mixins.touch-target(var(--tls-pagination-item-size));
84
+
75
85
  &:hover {
76
86
  outline: var(--tls-pagination-hover-outline);
77
87
  }
@@ -1,3 +1,5 @@
1
+ @use '../../mixins' as mixins;
2
+
1
3
  .tls-select {
2
4
  --tls-select-width: 200px;
3
5
  --tls-select-gap: var(--spacing-2);
@@ -105,6 +107,8 @@
105
107
 
106
108
  transform: translateY(-50%);
107
109
 
110
+ @include mixins.touch-target(var(--tls-select-clear-size));
111
+
108
112
  &:hover {
109
113
  color: var(--color-on-surface);
110
114
  }
@@ -16,6 +16,8 @@
16
16
  --tls-switch-handle-color: var(--color-surface);
17
17
  --tls-switch-border-color: var(--tls-switch-track-color);
18
18
 
19
+ position: relative;
20
+
19
21
  width: var(--tls-switch-width);
20
22
  height: var(--tls-switch-height);
21
23
 
@@ -38,6 +40,8 @@
38
40
  @include mixins.focus-ring;
39
41
  }
40
42
 
43
+ @include mixins.touch-target(var(--tls-switch-height));
44
+
41
45
  .tls-switch-input {
42
46
  display: none;
43
47
  }
@@ -0,0 +1,5 @@
1
+ @forward 'toggle-button';
2
+ @forward 'toggle-button-color';
3
+ @forward 'toggle-button-variant';
4
+ @forward 'toggle-button-size';
5
+ @forward 'toggle-button-checked';
@@ -0,0 +1,44 @@
1
+ @use '../../mixins' as mixins;
2
+
3
+ .tls-toggle-button {
4
+ // Pressed appearance: defaults to the filled treatment of the base hue, so a
5
+ // toggle is visually distinct out of the box even without checked overrides.
6
+ &--checked {
7
+ border-color: var(--tls-toggle-button-color);
8
+
9
+ @include mixins.variant-treatment(filled, 'tls-toggle-button');
10
+ }
11
+
12
+ // Re-points the shared token pair while pressed (--checked-<color> classes),
13
+ // emitted after the base color classes so the pressed hue wins the cascade.
14
+ @include mixins.color-variants('tls-toggle-button', $tonal: true, $modifier: 'checked-');
15
+
16
+ // Pressed-variant overrides (doubled class beats the default checked rule and,
17
+ // in the hover states, its hover rules). The explicit border/background resets
18
+ // undo the default checked fill for the treatments that don't set them.
19
+ &--checked.tls-toggle-button--checked-filled {
20
+ border-color: var(--tls-toggle-button-color);
21
+
22
+ @include mixins.variant-treatment(filled, 'tls-toggle-button');
23
+ }
24
+
25
+ &--checked.tls-toggle-button--checked-tonal {
26
+ border-color: transparent;
27
+
28
+ @include mixins.variant-treatment(tonal, 'tls-toggle-button');
29
+ }
30
+
31
+ &--checked.tls-toggle-button--checked-outlined {
32
+ background-color: transparent;
33
+
34
+ @include mixins.variant-treatment(outlined, 'tls-toggle-button');
35
+ }
36
+
37
+ &--checked.tls-toggle-button--checked-text {
38
+ border-color: transparent;
39
+
40
+ background-color: transparent;
41
+
42
+ @include mixins.variant-treatment(text, 'tls-toggle-button');
43
+ }
44
+ }
@@ -0,0 +1,9 @@
1
+ // No tonal-swap here: the checked state is a second, independently styled variant
2
+ // layer, so each variant reads its token pair directly (see _toggle-button-variant.scss
3
+ // and _toggle-button-checked.scss) to keep the base treatment from leaking across
4
+ // the checked boundary.
5
+ @use '../../mixins' as mixins;
6
+
7
+ .tls-toggle-button {
8
+ @include mixins.color-variants('tls-toggle-button', $tonal: true);
9
+ }
@@ -0,0 +1,31 @@
1
+ .tls-toggle-button {
2
+ &--sm {
3
+ --tls-toggle-button-height: var(--control-height-sm);
4
+ --tls-toggle-button-padding-inline: var(--spacing-3);
5
+
6
+ font-size: var(--font-label-small-size);
7
+ font-weight: var(--font-label-small-weight);
8
+ line-height: var(--font-label-small-line-height);
9
+ letter-spacing: var(--font-label-small-letter-spacing);
10
+ }
11
+
12
+ &--md {
13
+ --tls-toggle-button-height: var(--control-height-md);
14
+ --tls-toggle-button-padding-inline: var(--spacing-4);
15
+
16
+ font-size: var(--font-label-medium-size);
17
+ font-weight: var(--font-label-medium-weight);
18
+ line-height: var(--font-label-medium-line-height);
19
+ letter-spacing: var(--font-label-medium-letter-spacing);
20
+ }
21
+
22
+ &--lg {
23
+ --tls-toggle-button-height: var(--control-height-lg);
24
+ --tls-toggle-button-padding-inline: var(--spacing-5);
25
+
26
+ font-size: var(--font-label-large-size);
27
+ font-weight: var(--font-label-large-weight);
28
+ line-height: var(--font-label-large-line-height);
29
+ letter-spacing: var(--font-label-large-letter-spacing);
30
+ }
31
+ }
@@ -0,0 +1,21 @@
1
+ @use '../../mixins' as mixins;
2
+
3
+ .tls-toggle-button {
4
+ // Resting (unpressed) appearance. Each variant is gated off the checked state so
5
+ // the pressed appearance is owned entirely by the checked rules.
6
+ &--filled:not(.tls-toggle-button--checked) {
7
+ @include mixins.variant-treatment(filled, 'tls-toggle-button');
8
+ }
9
+
10
+ &--tonal:not(.tls-toggle-button--checked) {
11
+ @include mixins.variant-treatment(tonal, 'tls-toggle-button');
12
+ }
13
+
14
+ &--outlined:not(.tls-toggle-button--checked) {
15
+ @include mixins.variant-treatment(outlined, 'tls-toggle-button');
16
+ }
17
+
18
+ &--text:not(.tls-toggle-button--checked) {
19
+ @include mixins.variant-treatment(text, 'tls-toggle-button');
20
+ }
21
+ }
@@ -0,0 +1,79 @@
1
+ @use '../../mixins' as mixins;
2
+
3
+ .tls-toggle-button {
4
+ --tls-toggle-button-border-width: 1px;
5
+ --tls-toggle-button-border-radius: var(--radius-sm);
6
+
7
+ position: relative;
8
+
9
+ height: var(--tls-toggle-button-height);
10
+
11
+ padding-inline: var(--tls-toggle-button-padding-inline);
12
+
13
+ display: inline-flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+
17
+ border: var(--tls-toggle-button-border-width) solid transparent;
18
+ border-radius: var(--tls-toggle-button-border-radius);
19
+
20
+ background-color: transparent;
21
+
22
+ cursor: pointer;
23
+ user-select: none;
24
+
25
+ @include mixins.motion(essential) {
26
+ transition:
27
+ background-color var(--motion-fast) var(--motion-ease-in-out),
28
+ border-color var(--motion-fast) var(--motion-ease-in-out),
29
+ color var(--motion-fast) var(--motion-ease-in-out);
30
+ }
31
+
32
+ @include mixins.touch-target(var(--tls-toggle-button-height));
33
+
34
+ &:focus-visible {
35
+ @include mixins.focus-ring;
36
+ }
37
+
38
+ // Static-width label stack: both state labels share one grid cell, so the cell
39
+ // (and the button) always sizes to the wider label; the inactive label stays
40
+ // invisible but keeps reserving that width.
41
+ &__labels {
42
+ display: inline-grid;
43
+ justify-items: center;
44
+ }
45
+
46
+ &__label {
47
+ grid-area: 1 / 1;
48
+
49
+ &--hidden {
50
+ visibility: hidden;
51
+ }
52
+ }
53
+
54
+ &--icon-only {
55
+ aspect-ratio: 1 / 1;
56
+
57
+ padding-inline: 0;
58
+ }
59
+
60
+ &--fluid {
61
+ width: 100%;
62
+
63
+ display: flex;
64
+ }
65
+
66
+ &--disabled {
67
+ opacity: 0.5;
68
+
69
+ cursor: not-allowed;
70
+ }
71
+
72
+ &--readonly {
73
+ cursor: default;
74
+ }
75
+
76
+ &:not(.tls-toggle-button--disabled).tls-toggle-button--show-error {
77
+ border-color: var(--color-danger);
78
+ }
79
+ }
@@ -2,3 +2,5 @@
2
2
  @forward 'focus-ring';
3
3
  @forward 'motion';
4
4
  @forward 'panel-open';
5
+ @forward 'touch-target';
6
+ @forward 'variant-treatment';
@@ -0,0 +1,30 @@
1
+ // Expands a control's tap target to the accessible minimum (`--control-touch-target`,
2
+ // 44px) on touch devices, without changing its visual size or disturbing layout. A
3
+ // transparent `::after` overflows the control up to the target size and captures the
4
+ // taps; the control must therefore establish a positioning context (`position:
5
+ // relative`) and leave its `::after` free.
6
+ //
7
+ // `$height` is the control's rendered size (e.g. `var(--tls-button-height)`); the
8
+ // overflow is symmetric and `min(0px, …)` never shrinks a control already at or above
9
+ // the target.
10
+ //
11
+ // Gated on a coarse pointer and on the app-level opt-out reflected onto `:root`
12
+ // (`data-touch-targets='off'`). Absent that attribute the expansion is active — touch
13
+ // targets are accessible by default; disabling is opt-in through the UI config.
14
+ //
15
+ // Spacing constraint: the overflow reaches (`--control-touch-target` − visual size) / 2
16
+ // beyond each edge. Adjacent expandable controls must be separated by at least that much
17
+ // (a `gap`/`margin` ≥ the per-edge overflow) or their hit areas overlap; where they do,
18
+ // the later-in-DOM control wins the overlap (equal `z-index` falls back to paint order),
19
+ // stealing taps from its neighbour's edge. This mirrors the WCAG pairing of target size
20
+ // with target spacing — space clustered controls rather than packing them tight.
21
+ @mixin touch-target($height) {
22
+ @media (pointer: coarse) {
23
+ :root:not([data-touch-targets='off']) &::after {
24
+ content: '';
25
+
26
+ position: absolute;
27
+ inset: min(0px, calc((#{$height} - var(--control-touch-target)) / 2));
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,97 @@
1
+ // Appearance of a button-like control's variant treatment (filled / tonal /
2
+ // outlined / text / elevated), shared across controls (tls-button,
3
+ // tls-toggle-button). Maps the treatment onto the block's color token pair
4
+ // (see color-variants) and emits the hover / focus-visible / active feedback
5
+ // states, gated off the block's `--disabled` modifier.
6
+ //
7
+ // Emits declarations only — include it under the selector that scopes the
8
+ // treatment (a variant class, a pressed-state layer, …). The tonal treatment
9
+ // reads the container pair directly rather than relying on tonal-swap, so it
10
+ // is safe for blocks with a second variant layer.
11
+
12
+ @mixin _hover-focus-state($block) {
13
+ &:not(.#{$block}--disabled):hover,
14
+ &:not(.#{$block}--disabled):focus-visible {
15
+ @content;
16
+ }
17
+ }
18
+
19
+ @mixin _active-state($block) {
20
+ &:not(.#{$block}--disabled):active {
21
+ @content;
22
+ }
23
+ }
24
+
25
+ @mixin variant-treatment($treatment, $block) {
26
+ @if $treatment == filled {
27
+ background-color: var(--#{$block}-color);
28
+
29
+ color: var(--#{$block}-on-color);
30
+
31
+ @include _hover-focus-state($block) {
32
+ background-color: color-mix(in srgb, var(--#{$block}-color) 85%, var(--color-surface));
33
+ }
34
+
35
+ @include _active-state($block) {
36
+ background-color: color-mix(in srgb, var(--#{$block}-color) 75%, var(--color-surface));
37
+ }
38
+ } @else if $treatment == tonal {
39
+ background-color: var(--#{$block}-color-container);
40
+
41
+ color: var(--#{$block}-on-color-container);
42
+
43
+ @include _hover-focus-state($block) {
44
+ background-color: color-mix(
45
+ in srgb,
46
+ var(--#{$block}-color-container) 85%,
47
+ var(--color-surface)
48
+ );
49
+ }
50
+
51
+ @include _active-state($block) {
52
+ background-color: color-mix(
53
+ in srgb,
54
+ var(--#{$block}-color-container) 75%,
55
+ var(--color-surface)
56
+ );
57
+ }
58
+ } @else if $treatment == outlined {
59
+ border-color: var(--#{$block}-color);
60
+
61
+ color: var(--#{$block}-color);
62
+
63
+ @include _hover-focus-state($block) {
64
+ background-color: color-mix(in srgb, var(--#{$block}-color) 7.5%, var(--color-surface));
65
+ }
66
+
67
+ @include _active-state($block) {
68
+ background-color: color-mix(in srgb, var(--#{$block}-color) 15%, var(--color-surface));
69
+ }
70
+ } @else if $treatment == text {
71
+ color: var(--#{$block}-color);
72
+
73
+ @include _hover-focus-state($block) {
74
+ background-color: color-mix(in srgb, var(--#{$block}-color) 7.5%, var(--color-surface));
75
+ }
76
+
77
+ @include _active-state($block) {
78
+ background-color: color-mix(in srgb, var(--#{$block}-color) 15%, var(--color-surface));
79
+ }
80
+ } @else if $treatment == elevated {
81
+ box-shadow: var(--shadow-sm);
82
+
83
+ color: var(--#{$block}-color);
84
+
85
+ @include _hover-focus-state($block) {
86
+ background-color: color-mix(in srgb, var(--#{$block}-color) 7.5%, var(--color-surface));
87
+ }
88
+
89
+ @include _active-state($block) {
90
+ box-shadow: var(--shadow-xs);
91
+
92
+ background-color: color-mix(in srgb, var(--#{$block}-color) 15%, var(--color-surface));
93
+ }
94
+ } @else {
95
+ @error "Unknown variant treatment `#{$treatment}`.";
96
+ }
97
+ }
@@ -3,6 +3,9 @@
3
3
  --control-height-md: 36px;
4
4
  --control-height-lg: 44px;
5
5
 
6
+ // Minimum comfortable tap target on touch devices (Apple HIG; WCAG 2.5.5 Enhanced).
7
+ --control-touch-target: 44px;
8
+
6
9
  --control-focus-ring-width: 2px;
7
10
  --control-focus-ring-offset: 2px;
8
11
  --control-focus-ring-color: var(--color-primary);