mtrl 0.0.2 → 0.1.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 (52) hide show
  1. package/package.json +2 -2
  2. package/src/components/button/styles.scss +198 -161
  3. package/src/components/checkbox/checkbox.js +4 -3
  4. package/src/components/checkbox/styles.scss +105 -55
  5. package/src/components/container/styles.scss +65 -58
  6. package/src/components/list/styles.scss +240 -11
  7. package/src/components/menu/features/items-manager.js +5 -1
  8. package/src/components/menu/styles.scss +37 -30
  9. package/src/components/navigation/constants.js +19 -54
  10. package/src/components/navigation/styles.scss +406 -6
  11. package/src/components/snackbar/styles.scss +46 -17
  12. package/src/components/switch/styles.scss +104 -40
  13. package/src/components/switch/switch.js +1 -1
  14. package/src/components/textfield/styles.scss +351 -5
  15. package/src/core/build/_ripple.scss +79 -0
  16. package/src/core/compose/features/disabled.js +27 -7
  17. package/src/core/compose/features/input.js +9 -1
  18. package/src/core/compose/features/textinput.js +16 -20
  19. package/src/core/dom/create.js +0 -1
  20. package/src/styles/abstract/_mixins.scss +9 -7
  21. package/src/styles/abstract/_theme.scss +157 -0
  22. package/src/styles/abstract/_variables.scss +72 -6
  23. package/src/styles/base/_reset.scss +86 -0
  24. package/src/styles/base/_typography.scss +155 -0
  25. package/src/styles/main.scss +104 -57
  26. package/src/styles/themes/_base-theme.scss +2 -27
  27. package/src/styles/themes/_baseline.scss +64 -39
  28. package/src/styles/utilities/_color.scss +154 -0
  29. package/src/styles/utilities/_flexbox.scss +194 -0
  30. package/src/styles/utilities/_spacing.scss +139 -0
  31. package/src/styles/utilities/_typography.scss +178 -0
  32. package/src/styles/utilities/_visibility.scss +142 -0
  33. package/test/components/button.test.js +46 -34
  34. package/test/components/checkbox.test.js +238 -0
  35. package/test/components/list.test.js +105 -0
  36. package/test/components/menu.test.js +385 -0
  37. package/test/components/navigation.test.js +227 -0
  38. package/test/components/snackbar.test.js +234 -0
  39. package/test/components/switch.test.js +186 -0
  40. package/test/components/textfield.test.js +314 -0
  41. package/test/core/ripple.test.js +21 -120
  42. package/test/setup.js +152 -239
  43. package/src/components/list/styles/_list-item.scss +0 -142
  44. package/src/components/list/styles/_list.scss +0 -89
  45. package/src/components/list/styles/_variables.scss +0 -13
  46. package/src/components/navigation/styles/_bar.scss +0 -51
  47. package/src/components/navigation/styles/_base.scss +0 -129
  48. package/src/components/navigation/styles/_drawer.scss +0 -169
  49. package/src/components/navigation/styles/_rail.scss +0 -65
  50. package/src/components/textfield/styles/base.scss +0 -107
  51. package/src/components/textfield/styles/filled.scss +0 -58
  52. package/src/components/textfield/styles/outlined.scss +0 -66
@@ -1,8 +1,14 @@
1
- // src/components/checkbox/styles.scss
2
- @use 'sass:map';
3
- @use '../../styles/abstract/config' as c;
1
+ // src/components/checkbox/_checkbox.scss
2
+ @use '../../styles/abstract/base' as base;
3
+ @use '../../styles/abstract/variables' as v;
4
+ @use '../../styles/abstract/functions' as f;
5
+ @use '../../styles/abstract/mixins' as m;
6
+ @use '../../styles/abstract/theme' as t;
4
7
 
5
- .#{c.$prefix}-checkbox {
8
+ $component: '#{base.$prefix}-checkbox';
9
+
10
+ .#{$component} {
11
+ // Base styles
6
12
  display: inline-flex;
7
13
  align-items: center;
8
14
  position: relative;
@@ -10,6 +16,7 @@
10
16
  padding: 4px 0;
11
17
  user-select: none;
12
18
 
19
+ // Input (visually hidden but accessible)
13
20
  &-input {
14
21
  position: absolute;
15
22
  opacity: 0;
@@ -23,22 +30,24 @@
23
30
  cursor: not-allowed;
24
31
  }
25
32
 
26
- &:focus-visible ~ .#{c.$prefix}-checkbox-icon {
27
- outline: 2px solid var(--mtrl-sys-color-primary);
33
+ // Focus indicator
34
+ &:focus-visible ~ .#{$component}-icon {
35
+ outline: 2px solid t.color('primary');
28
36
  outline-offset: 2px;
29
37
  }
30
38
  }
31
39
 
40
+ // Checkbox visual element
32
41
  &-icon {
33
42
  position: relative;
34
43
  width: 18px;
35
44
  height: 18px;
36
- border-radius: 2px;
37
- background-color: var(--mtrl-sys-color-surface-container-highest);
38
- border: 2px solid var(--mtrl-sys-color-outline);
39
- transition: background-color map.get(c.$motion, 'duration-short4') map.get(c.$motion, 'easing-standard'),
40
- border-color map.get(c.$motion, 'duration-short4') map.get(c.$motion, 'easing-standard');
45
+ border-radius: f.get-shape('tiny');
46
+ background-color: t.color('surface-container-highest');
47
+ border: 2px solid t.color('outline');
48
+ @include m.motion-transition(background-color, border-color);
41
49
 
50
+ // Check mark icon
42
51
  svg {
43
52
  position: absolute;
44
53
  top: 50%;
@@ -47,22 +56,23 @@
47
56
  height: 18px;
48
57
  transform: translate(-50%, -50%) scale(0);
49
58
  fill: currentColor;
50
- color: var(--mtrl-sys-color-on-primary);
51
- transition: transform map.get(c.$motion, 'duration-short4') map.get(c.$motion, 'easing-emphasized');
59
+ color: t.color('on-primary');
60
+ transition: transform v.motion('duration-short4') v.motion('easing-emphasized');
52
61
  }
53
62
  }
54
63
 
64
+ // Label styling
55
65
  &-label {
56
- @include c.typography('body-large');
66
+ @include m.typography('body-large');
57
67
  margin-left: 12px;
58
- color: var(--mtrl-sys-color-on-surface);
68
+ color: t.color('on-surface');
59
69
  }
60
70
 
61
71
  // Label position variants
62
72
  &--label-start {
63
73
  flex-direction: row-reverse;
64
74
 
65
- .#{c.$prefix}-checkbox-label {
75
+ .#{$component}-label {
66
76
  margin-left: 0;
67
77
  margin-right: 12px;
68
78
  }
@@ -71,42 +81,44 @@
71
81
  &--label-end {
72
82
  flex-direction: row;
73
83
 
74
- .#{c.$prefix}-checkbox-label {
84
+ .#{$component}-label {
75
85
  margin-left: 12px;
76
86
  margin-right: 0;
77
87
  }
78
88
  }
79
89
 
80
- @include c.rtl {
90
+ // RTL support
91
+ @include m.rtl {
81
92
  &--label-start {
82
- .#{c.$prefix}-checkbox-label {
93
+ .#{$component}-label {
83
94
  margin-left: 12px;
84
95
  margin-right: 0;
85
96
  }
86
97
  }
87
98
 
88
99
  &--label-end {
89
- .#{c.$prefix}-checkbox-label {
100
+ .#{$component}-label {
90
101
  margin-left: 0;
91
102
  margin-right: 12px;
92
103
  }
93
104
  }
94
105
  }
95
106
 
96
- // States
97
- .#{c.$prefix}-checkbox-input:checked ~ .#{c.$prefix}-checkbox-icon {
98
- background-color: var(--mtrl-sys-color-primary);
99
- border-color: var(--mtrl-sys-color-primary);
107
+ // Checked state
108
+ .#{$component}-input:checked ~ .#{$component}-icon {
109
+ background-color: t.color('primary');
110
+ border-color: t.color('primary');
100
111
 
101
112
  svg {
102
113
  transform: translate(-50%, -50%) scale(1);
103
114
  }
104
115
  }
105
116
 
117
+ // Indeterminate state
106
118
  &--indeterminate {
107
- .#{c.$prefix}-checkbox-icon {
108
- background-color: var(--mtrl-sys-color-primary);
109
- border-color: var(--mtrl-sys-color-primary);
119
+ .#{$component}-icon {
120
+ background-color: t.color('primary');
121
+ border-color: t.color('primary');
110
122
 
111
123
  &::after {
112
124
  content: '';
@@ -116,7 +128,7 @@
116
128
  transform: translate(-50%, -50%);
117
129
  width: 10px;
118
130
  height: 2px;
119
- background-color: var(--mtrl-sys-color-on-primary);
131
+ background-color: t.color('on-primary');
120
132
  }
121
133
 
122
134
  svg {
@@ -125,59 +137,97 @@
125
137
  }
126
138
  }
127
139
 
140
+ // Disabled state
128
141
  &--disabled {
129
- opacity: .5;
142
+ opacity: 0.38;
143
+
144
+ .#{$component}-input {
145
+ cursor: not-allowed;
146
+ }
130
147
  }
131
148
 
132
- // Variants
149
+ // Outlined variant
133
150
  &--outlined {
134
- .#{c.$prefix}-checkbox-icon {
151
+ .#{$component}-icon {
135
152
  background-color: transparent;
136
153
  }
137
154
 
138
- .#{c.$prefix}-checkbox-input:checked ~ .#{c.$prefix}-checkbox-icon {
155
+ .#{$component}-input:checked ~ .#{$component}-icon {
139
156
  background-color: transparent;
140
- border-color: var(--mtrl-sys-color-primary);
157
+ border-color: t.color('primary');
141
158
 
142
159
  svg {
143
- color: var(--mtrl-sys-color-primary);
160
+ color: t.color('primary');
144
161
  }
145
162
  }
146
163
 
147
- &.#{c.$prefix}-checkbox--indeterminate {
148
- .#{c.$prefix}-checkbox-icon::after {
149
- background-color: var(--mtrl-sys-color-primary);
164
+ &.#{$component}--indeterminate {
165
+ .#{$component}-icon::after {
166
+ background-color: t.color('primary');
150
167
  }
151
168
  }
152
169
  }
153
170
 
154
- // Hover effects
171
+ // State layer effects
155
172
  &:not(&--disabled) {
156
- .#{c.$prefix}-checkbox-input:hover ~ .#{c.$prefix}-checkbox-icon {
157
- &::before {
158
- content: '';
159
- position: absolute;
160
- top: -8px;
161
- left: -8px;
162
- right: -8px;
163
- bottom: -8px;
164
- background-color: var(--mtrl-sys-color-on-surface);
165
- opacity: 0.08;
166
- border-radius: 4px;
167
- }
173
+ // Common state layer structure
174
+ .#{$component}-input:hover ~ .#{$component}-icon::before,
175
+ .#{$component}-input:active ~ .#{$component}-icon::before {
176
+ content: '';
177
+ position: absolute;
178
+ top: -12px;
179
+ left: -12px;
180
+ right: -12px;
181
+ bottom: -12px;
182
+ border-radius: 50%;
183
+ background-color: t.color('on-surface');
184
+ }
185
+
186
+ // Hover state
187
+ .#{$component}-input:hover ~ .#{$component}-icon::before {
188
+ opacity: v.state('hover-state-layer-opacity');
189
+
190
+ }
191
+
192
+ // Hover checked state
193
+ .#{$component}-input:checked:hover ~ .#{$component}-icon::before {
194
+ opacity: v.state('pressed-state-layer-opacity');
195
+ background-color: t.color('primary');
196
+ }
197
+ // Active/pressed state
198
+ .#{$component}-input:active ~ .#{$component}-icon::before {
199
+ opacity: v.state('pressed-state-layer-opacity');
200
+ background-color: t.color('on-surface');
168
201
  }
169
202
  }
170
203
 
171
- @include c.reduced-motion {
172
- .#{c.$prefix}-checkbox-icon,
173
- .#{c.$prefix}-checkbox-icon svg {
204
+ // Accessibility
205
+ @include m.reduced-motion {
206
+ .#{$component}-icon,
207
+ .#{$component}-icon svg {
174
208
  transition: none;
175
209
  }
176
210
  }
177
211
 
178
- @include c.high-contrast {
179
- .#{c.$prefix}-checkbox-icon {
212
+ @include m.high-contrast {
213
+ .#{$component}-icon {
180
214
  border-width: 2px;
181
215
  }
182
216
  }
217
+
218
+ // Touch target
219
+ // @include m.touch-target;
220
+ }
221
+
222
+ // Checkbox group layout
223
+ .#{$component}-group {
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 8px;
227
+
228
+ &--horizontal {
229
+ flex-direction: row;
230
+ gap: 16px;
231
+ flex-wrap: wrap;
232
+ }
183
233
  }
@@ -1,59 +1,66 @@
1
- // src/components/container/styles.scss
2
- @use 'sass:map';
3
- @use '../../styles/abstract/config' as c;
4
-
5
- .#{c.$prefix}-container {
6
- @include c.shape('medium');
7
- @include c.motion-transition(
8
- background-color,
9
- box-shadow
10
- );
11
-
12
- padding: 16px;
13
- background-color: var(--mtrl-sys-color-surface-container);
14
-
15
- &--low {
16
- background-color: var(--mtrl-sys-color-surface-container-low);
17
- }
18
-
19
- &--lowest {
20
- background-color: var(--mtrl-sys-color-surface-container-lowest);
21
- }
22
-
23
- &--high {
24
- background-color: var(--mtrl-sys-color-surface-container-high);
25
- }
26
-
27
- &--highest {
28
- background-color: var(--mtrl-sys-color-surface-container-highest);
29
- }
30
-
31
- // Elevation variants
32
- &--elevation-0 {
33
- @include c.elevation(0);
34
- }
35
-
36
- &--elevation-1 {
37
- @include c.elevation(1);
38
- }
39
-
40
- &--elevation-2 {
41
- @include c.elevation(2);
42
- }
43
-
44
- &--elevation-3 {
45
- @include c.elevation(3);
46
- }
47
-
48
- &--elevation-4 {
49
- @include c.elevation(4);
50
- }
51
-
52
- @include c.reduced-motion {
53
- transition: none;
54
- }
55
-
56
- @include c.high-contrast {
57
- border: 1px solid currentColor;
58
- }
1
+ // src/components/container/_container.scss
2
+ @use '../../styles/abstract/base' as base;
3
+ @use '../../styles/abstract/variables' as v;
4
+ @use '../../styles/abstract/functions' as f;
5
+ @use '../../styles/abstract/mixins' as m;
6
+ @use '../../styles/abstract/theme' as t;
7
+
8
+ $component: '#{base.$prefix}-container';
9
+
10
+ .#{$component} {
11
+ @include m.shape('medium');
12
+ @include m.motion-transition(
13
+ background-color,
14
+ box-shadow
15
+ );
16
+
17
+ padding: 16px;
18
+ background-color: t.color('surface-container');
19
+
20
+ // Surface container variants
21
+ &--low {
22
+ background-color: t.color('surface-container-low');
23
+ }
24
+
25
+ &--lowest {
26
+ background-color: t.color('surface-container-lowest');
27
+ }
28
+
29
+ &--high {
30
+ background-color: t.color('surface-container-high');
31
+ }
32
+
33
+ &--highest {
34
+ background-color: t.color('surface-container-highest');
35
+ }
36
+
37
+ // Elevation variants
38
+ &--elevation-0 {
39
+ @include m.elevation(0);
40
+ }
41
+
42
+ &--elevation-1 {
43
+ @include m.elevation(1);
44
+ }
45
+
46
+ &--elevation-2 {
47
+ @include m.elevation(2);
48
+ }
49
+
50
+ &--elevation-3 {
51
+ @include m.elevation(3);
52
+ }
53
+
54
+ &--elevation-4 {
55
+ @include m.elevation(4);
56
+ }
57
+
58
+ // Accessibility
59
+ @include m.reduced-motion {
60
+ transition: none;
61
+ }
62
+
63
+ @include m.high-contrast {
64
+ border: 1px solid currentColor;
65
+ }
59
66
  }
@@ -1,19 +1,248 @@
1
- // src/components/list/styles.scss
2
-
3
- // Import core configuration and utilities
4
- @use '../../styles/abstract/config' as c;
1
+ // src/components/list/_list.scss
2
+ @use '../../styles/abstract/base' as base;
3
+ @use '../../styles/abstract/variables' as v;
4
+ @use '../../styles/abstract/functions' as f;
5
5
  @use '../../styles/abstract/mixins' as m;
6
- @use './styles/variables' as v;
7
- @use './styles/list';
8
- @use './styles/list-item';
6
+ @use '../../styles/abstract/theme' as t;
7
+
8
+ // Component variables
9
+ $component: '#{base.$prefix}-list';
10
+ $list-item-height: 48px !default;
11
+ $list-item-dense-height: 40px !default;
12
+ $list-padding: 8px !default;
13
+ $list-item-padding: 16px !default;
14
+ $list-section-padding: 16px !default;
9
15
 
10
16
  // Component-specific mixins
11
17
  @mixin list-item-hover {
12
- @include c.state-layer(v.$list-hover-state-layer-color, 'hover');
18
+ @include m.state-layer(t.color('on-surface'), 'hover');
13
19
  }
14
20
 
15
21
  @mixin list-section-title {
16
- @include c.typography('label-large');
17
- color: v.$list-section-title-color;
18
- padding: v.$list-section-padding v.$list-item-padding 8px;
22
+ @include m.typography('label-large');
23
+ color: t.color('primary');
24
+ padding: $list-section-padding $list-item-padding 8px;
25
+ }
26
+
27
+ // LIST COMPONENT
28
+ .#{$component} {
29
+ display: flex;
30
+ flex-direction: column;
31
+ padding: $list-padding 0;
32
+ min-width: 200px;
33
+ background-color: t.color('surface');
34
+
35
+ // Sections
36
+ &-section {
37
+ display: flex;
38
+ flex-direction: column;
39
+ width: 100%;
40
+
41
+ &:not(:last-child) {
42
+ margin-bottom: $list-padding;
43
+ }
44
+ }
45
+
46
+ &-section-title {
47
+ @include list-section-title;
48
+ }
49
+
50
+ // Dividers
51
+ &-divider {
52
+ height: 1px;
53
+ margin: $list-padding 0;
54
+ background-color: t.color('outline-variant');
55
+ }
56
+
57
+ // Selection states
58
+ &[data-type="single"],
59
+ &[data-type="multi"],
60
+ &[data-type="radio"] {
61
+ .#{$component}-item {
62
+ cursor: pointer;
63
+
64
+ &--selected {
65
+ background-color: t.color('secondary-container');
66
+ color: t.color('on-secondary-container');
67
+
68
+ &:hover {
69
+ background-color: t.color('secondary-container');
70
+ @include m.state-layer(t.color('on-secondary-container'), 'hover');
71
+ }
72
+
73
+ .#{$component}-item-supporting {
74
+ color: t.color('on-secondary-container');
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ // Dense variant
81
+ &--dense {
82
+ .#{$component}-item {
83
+ min-height: $list-item-dense-height;
84
+ }
85
+ }
86
+
87
+ // Disabled state
88
+ &--disabled {
89
+ pointer-events: none;
90
+ opacity: 0.38;
91
+ }
92
+
93
+ // RTL Support
94
+ @include m.rtl {
95
+ .#{$component}-section-title {
96
+ text-align: right;
97
+ }
98
+ }
99
+
100
+ // High contrast mode
101
+ @include m.high-contrast {
102
+ border: 1px solid currentColor;
103
+
104
+ &-divider {
105
+ border-top: 1px solid currentColor;
106
+ background: none;
107
+ }
108
+ }
109
+
110
+ // LIST ITEM STYLES
111
+ &-item {
112
+ display: flex;
113
+ align-items: center;
114
+ min-height: $list-item-height;
115
+ padding: $list-padding $list-item-padding;
116
+ position: relative;
117
+ gap: 16px;
118
+ cursor: pointer;
119
+ color: t.color('on-surface');
120
+ @include m.motion-transition(background-color);
121
+
122
+ &:hover {
123
+ @include list-item-hover;
124
+ }
125
+
126
+ &:focus-visible {
127
+ @include m.state-layer(t.color('on-surface'), 'focus');
128
+ outline: none;
129
+ }
130
+
131
+ &:active {
132
+ @include m.state-layer(t.color('on-surface'), 'pressed');
133
+ }
134
+
135
+ // Content layout
136
+ &-content {
137
+ flex: 1;
138
+ min-width: 0; // Enable text truncation
139
+ display: flex;
140
+ flex-direction: column;
141
+ justify-content: center;
142
+ }
143
+
144
+ // Text elements
145
+ &-text {
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: 4px;
149
+ }
150
+
151
+ &-overline {
152
+ @include m.typography('label-small');
153
+ color: t.color('on-surface-variant');
154
+ }
155
+
156
+ &-headline {
157
+ @include m.typography('body-large');
158
+ color: t.color('on-surface');
159
+ }
160
+
161
+ &-supporting {
162
+ @include m.typography('body-medium');
163
+ color: t.color('on-surface-variant');
164
+ }
165
+
166
+ &-meta {
167
+ @include m.typography('label-small');
168
+ color: t.color('on-surface-variant');
169
+ margin-top: 4px;
170
+ }
171
+
172
+ // Leading/trailing elements
173
+ &-leading,
174
+ &-trailing {
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ flex-shrink: 0;
179
+ }
180
+
181
+ &-leading {
182
+ width: 24px;
183
+ height: 24px;
184
+ color: t.color('on-surface-variant');
185
+
186
+ svg {
187
+ width: 100%;
188
+ height: 100%;
189
+ }
190
+ }
191
+
192
+ &-trailing {
193
+ color: t.color('on-surface-variant');
194
+ }
195
+
196
+ // Vertical layout variant
197
+ &.vertical {
198
+ min-height: 72px;
199
+ padding: 12px $list-item-padding;
200
+
201
+ .#{$component}-item {
202
+ &-content {
203
+ flex-direction: column;
204
+ gap: 4px;
205
+ }
206
+
207
+ &-meta {
208
+ margin-top: $list-padding;
209
+ }
210
+ }
211
+ }
212
+
213
+ // States
214
+ &--selected {
215
+ background-color: t.color('secondary-container');
216
+ color: t.color('on-secondary-container');
217
+
218
+ .#{$component}-item {
219
+ &-leading,
220
+ &-trailing {
221
+ color: t.color('on-secondary-container');
222
+ }
223
+ }
224
+ }
225
+
226
+ &--disabled {
227
+ opacity: 0.38;
228
+ pointer-events: none;
229
+ }
230
+
231
+ // RTL Support
232
+ @include m.rtl {
233
+ .#{$component}-item {
234
+ &-text {
235
+ text-align: right;
236
+ }
237
+ }
238
+ }
239
+
240
+ // High contrast mode
241
+ @include m.high-contrast {
242
+ &--selected {
243
+ outline: 2px solid currentColor;
244
+ outline-offset: -2px;
245
+ }
246
+ }
247
+ }
19
248
  }
@@ -331,6 +331,10 @@ export const withItemsManager = (config) => (component) => {
331
331
  removeItem (name) {
332
332
  if (!name) return this
333
333
 
334
+ // First, ensure we remove the item from our internal map
335
+ itemsMap.delete(name)
336
+
337
+ // Now try to remove the item from the DOM
334
338
  const item = list.querySelector(`[data-name="${name}"]`)
335
339
  if (item) {
336
340
  // Remove event listeners
@@ -344,8 +348,8 @@ export const withItemsManager = (config) => (component) => {
344
348
  submenus.delete(name)
345
349
  }
346
350
 
351
+ // Remove the item from the DOM
347
352
  item.remove()
348
- itemsMap.delete(name)
349
353
  }
350
354
 
351
355
  return this