mtrl 0.2.5 → 0.2.6
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.
- package/package.json +1 -1
- package/src/components/badge/_styles.scss +9 -9
- package/src/components/button/_styles.scss +0 -56
- package/src/components/button/button.ts +0 -2
- package/src/components/button/constants.ts +0 -6
- package/src/components/button/index.ts +2 -2
- package/src/components/button/types.ts +1 -7
- package/src/components/card/_styles.scss +67 -25
- package/src/components/card/api.ts +54 -3
- package/src/components/card/card.ts +33 -2
- package/src/components/card/config.ts +143 -21
- package/src/components/card/constants.ts +20 -19
- package/src/components/card/content.ts +299 -2
- package/src/components/card/features.ts +155 -4
- package/src/components/card/index.ts +31 -9
- package/src/components/card/types.ts +138 -15
- package/src/components/chip/chip.ts +1 -9
- package/src/components/chip/constants.ts +0 -10
- package/src/components/chip/index.ts +1 -1
- package/src/components/chip/types.ts +1 -4
- package/src/components/progress/_styles.scss +0 -65
- package/src/components/progress/config.ts +1 -2
- package/src/components/progress/constants.ts +0 -14
- package/src/components/progress/index.ts +1 -1
- package/src/components/progress/progress.ts +1 -4
- package/src/components/progress/types.ts +1 -4
- package/src/components/radios/_styles.scss +0 -45
- package/src/components/radios/api.ts +85 -60
- package/src/components/radios/config.ts +1 -2
- package/src/components/radios/constants.ts +0 -9
- package/src/components/radios/index.ts +1 -1
- package/src/components/radios/radio.ts +34 -11
- package/src/components/radios/radios.ts +2 -1
- package/src/components/radios/types.ts +1 -7
- package/src/components/slider/_styles.scss +149 -155
- package/src/components/slider/accessibility.md +59 -0
- package/src/components/slider/config.ts +4 -6
- package/src/components/slider/features/disabled.ts +41 -16
- package/src/components/slider/features/interactions.ts +153 -18
- package/src/components/slider/features/keyboard.ts +127 -6
- package/src/components/slider/features/structure.ts +32 -5
- package/src/components/slider/features/ui.ts +18 -8
- package/src/components/tabs/_styles.scss +285 -155
- package/src/components/tabs/api.ts +178 -400
- package/src/components/tabs/config.ts +46 -52
- package/src/components/tabs/constants.ts +85 -8
- package/src/components/tabs/features.ts +401 -0
- package/src/components/tabs/index.ts +60 -3
- package/src/components/tabs/indicator.ts +225 -0
- package/src/components/tabs/responsive.ts +144 -0
- package/src/components/tabs/scroll-indicators.ts +149 -0
- package/src/components/tabs/state.ts +186 -0
- package/src/components/tabs/tab-api.ts +258 -0
- package/src/components/tabs/tab.ts +255 -0
- package/src/components/tabs/tabs.ts +50 -31
- package/src/components/tabs/types.ts +324 -128
- package/src/components/tabs/utils.ts +107 -0
- package/src/components/textfield/_styles.scss +0 -98
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/constants.ts +0 -14
- package/src/components/textfield/index.ts +2 -2
- package/src/components/textfield/textfield.ts +0 -2
- package/src/components/textfield/types.ts +1 -4
- package/src/core/compose/component.ts +1 -1
- package/src/core/compose/features/badge.ts +79 -0
- package/src/core/compose/features/index.ts +3 -1
- package/src/styles/abstract/_theme.scss +106 -2
- package/src/components/card/actions.ts +0 -48
- package/src/components/card/header.ts +0 -88
- package/src/components/card/media.ts +0 -52
package/package.json
CHANGED
|
@@ -112,18 +112,18 @@ $component: '#{base.$prefix}-badge';
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
&--success {
|
|
115
|
-
background-color: t.color('success'
|
|
116
|
-
color: t.color('on-success'
|
|
115
|
+
background-color: t.color('success');
|
|
116
|
+
color: t.color('on-success');
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
&--warning {
|
|
120
|
-
background-color: t.color('warning'
|
|
121
|
-
color: t.color('on-warning'
|
|
120
|
+
background-color: t.color('warning');
|
|
121
|
+
color: t.color('on-warning');
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
&--info {
|
|
125
|
-
background-color: t.color('info'
|
|
126
|
-
color: t.color('on-info'
|
|
125
|
+
background-color: t.color('info');
|
|
126
|
+
color: t.color('on-info');
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Outline variant
|
|
@@ -148,15 +148,15 @@ $component: '#{base.$prefix}-badge';
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
&.#{$component}--success {
|
|
151
|
-
color: t.color('success'
|
|
151
|
+
color: t.color('success');
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
&.#{$component}--warning {
|
|
155
|
-
color: t.color('warning'
|
|
155
|
+
color: t.color('warning');
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
&.#{$component}--info {
|
|
159
|
-
color: t.color('info'
|
|
159
|
+
color: t.color('info');
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
$component: '#{base.$prefix}-button';
|
|
9
9
|
|
|
10
10
|
.#{$component} {
|
|
11
|
-
// @include m.touch-target;
|
|
12
11
|
// Base styles
|
|
13
12
|
position: relative;
|
|
14
13
|
display: inline-flex;
|
|
@@ -95,16 +94,6 @@ $component: '#{base.$prefix}-button';
|
|
|
95
94
|
.#{$component}-icon {
|
|
96
95
|
margin: 0;
|
|
97
96
|
}
|
|
98
|
-
|
|
99
|
-
.#{$component}--small {
|
|
100
|
-
width: 32px;
|
|
101
|
-
height: 32px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.#{$component}--large {
|
|
105
|
-
width: 48px;
|
|
106
|
-
height: 48px;
|
|
107
|
-
}
|
|
108
97
|
}
|
|
109
98
|
|
|
110
99
|
// Ripple container
|
|
@@ -119,7 +108,6 @@ $component: '#{base.$prefix}-button';
|
|
|
119
108
|
|
|
120
109
|
&--disabled {
|
|
121
110
|
opacity: 0.38
|
|
122
|
-
|
|
123
111
|
}
|
|
124
112
|
|
|
125
113
|
// Variants
|
|
@@ -136,11 +124,6 @@ $component: '#{base.$prefix}-button';
|
|
|
136
124
|
@include m.state-layer(t.color('on-primary'), 'pressed');
|
|
137
125
|
@include m.elevation(0);
|
|
138
126
|
}
|
|
139
|
-
|
|
140
|
-
// &:disabled {
|
|
141
|
-
// background-color: t.alpha('on-surface', 0.12);
|
|
142
|
-
// color: t.alpha('on-surface', 0.38);
|
|
143
|
-
// }
|
|
144
127
|
}
|
|
145
128
|
|
|
146
129
|
// Elevated button variant
|
|
@@ -170,10 +153,6 @@ $component: '#{base.$prefix}-button';
|
|
|
170
153
|
.#{$component}-icon {
|
|
171
154
|
color: t.color('primary');
|
|
172
155
|
}
|
|
173
|
-
|
|
174
|
-
// &:disabled .#{$component}-icon {
|
|
175
|
-
// color: t.alpha('on-surface', 0.38);
|
|
176
|
-
// }
|
|
177
156
|
}
|
|
178
157
|
|
|
179
158
|
&--tonal {
|
|
@@ -189,11 +168,6 @@ $component: '#{base.$prefix}-button';
|
|
|
189
168
|
@include m.state-layer(t.color('on-secondary-container'), 'pressed');
|
|
190
169
|
@include m.elevation(0);
|
|
191
170
|
}
|
|
192
|
-
|
|
193
|
-
// &:disabled {
|
|
194
|
-
// background-color: t.alpha('on-surface', 0.12);
|
|
195
|
-
// color: t.alpha('on-surface', 0.38);
|
|
196
|
-
// }
|
|
197
171
|
}
|
|
198
172
|
|
|
199
173
|
&--outlined {
|
|
@@ -208,11 +182,6 @@ $component: '#{base.$prefix}-button';
|
|
|
208
182
|
&:active {
|
|
209
183
|
@include m.state-layer(t.color('primary'), 'pressed');
|
|
210
184
|
}
|
|
211
|
-
|
|
212
|
-
// &:disabled {
|
|
213
|
-
// border-color: t.alpha('on-surface', 0.12);
|
|
214
|
-
// color: t.alpha('on-surface', 0.38);
|
|
215
|
-
// }
|
|
216
185
|
}
|
|
217
186
|
|
|
218
187
|
&--text {
|
|
@@ -233,36 +202,11 @@ $component: '#{base.$prefix}-button';
|
|
|
233
202
|
}
|
|
234
203
|
}
|
|
235
204
|
|
|
236
|
-
// Size variants
|
|
237
|
-
&--small {
|
|
238
|
-
height: 32px;
|
|
239
|
-
min-width: 48px;
|
|
240
|
-
padding: 0 v.button('padding-horizontal-small');
|
|
241
|
-
font-size: 13px;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
&--large {
|
|
245
|
-
height: 48px;
|
|
246
|
-
min-width: 78px;
|
|
247
|
-
padding: 0 32px;
|
|
248
|
-
font-size: 16px;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
205
|
// Special case for icon-only buttons
|
|
252
206
|
&--icon-only {
|
|
253
207
|
min-width: v.button('height');
|
|
254
208
|
width: v.button('height');
|
|
255
209
|
padding: 0;
|
|
256
210
|
border-radius: 50%;
|
|
257
|
-
|
|
258
|
-
&.#{$component}--small {
|
|
259
|
-
min-width: 32px;
|
|
260
|
-
width: 32px;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
&.#{$component}--large {
|
|
264
|
-
min-width: 48px;
|
|
265
|
-
width: 48px;
|
|
266
|
-
}
|
|
267
211
|
}
|
|
268
212
|
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
withText,
|
|
8
8
|
withIcon,
|
|
9
9
|
withVariant,
|
|
10
|
-
withSize,
|
|
11
10
|
withRipple,
|
|
12
11
|
withDisabled,
|
|
13
12
|
withLifecycle
|
|
@@ -31,7 +30,6 @@ const createButton = (config: ButtonConfig = {}) => {
|
|
|
31
30
|
withEvents(),
|
|
32
31
|
withElement(getElementConfig(baseConfig)),
|
|
33
32
|
withVariant(baseConfig),
|
|
34
|
-
withSize(baseConfig),
|
|
35
33
|
withText(baseConfig),
|
|
36
34
|
withIcon(baseConfig),
|
|
37
35
|
withDisabled(baseConfig),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// src/components/button/index.ts
|
|
2
2
|
export { default } from './button'
|
|
3
|
-
export { BUTTON_VARIANTS
|
|
4
|
-
export { ButtonConfig, ButtonComponent } from './types'
|
|
3
|
+
export { BUTTON_VARIANTS } from './constants'
|
|
4
|
+
export { ButtonConfig, ButtonComponent } from './types'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/button/types.ts
|
|
2
|
-
import { BUTTON_VARIANTS
|
|
2
|
+
import { BUTTON_VARIANTS } from './constants';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Configuration interface for the Button component
|
|
@@ -12,12 +12,6 @@ export interface ButtonConfig {
|
|
|
12
12
|
*/
|
|
13
13
|
variant?: keyof typeof BUTTON_VARIANTS | string;
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Button size
|
|
17
|
-
* @default 'medium'
|
|
18
|
-
*/
|
|
19
|
-
size?: keyof typeof BUTTON_SIZES | string;
|
|
20
|
-
|
|
21
15
|
/**
|
|
22
16
|
* Whether the button is initially disabled
|
|
23
17
|
* @default false
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/components/card/
|
|
1
|
+
// src/components/card/_styles.scss
|
|
2
2
|
@use '../../styles/abstract/base' as base;
|
|
3
3
|
@use '../../styles/abstract/variables' as v;
|
|
4
4
|
@use '../../styles/abstract/functions' as f;
|
|
@@ -13,23 +13,33 @@ $component: '#{base.$prefix}-card';
|
|
|
13
13
|
display: flex;
|
|
14
14
|
flex-direction: column;
|
|
15
15
|
box-sizing: border-box;
|
|
16
|
-
border-radius:
|
|
16
|
+
border-radius: f.get-shape('medium'); // Use function for MD3 standard shape
|
|
17
17
|
background-color: t.color('surface');
|
|
18
18
|
color: t.color('on-surface');
|
|
19
19
|
overflow: hidden;
|
|
20
|
-
width:
|
|
21
|
-
--card-elevation:
|
|
20
|
+
width: v.card('width'); // Use card width variable
|
|
21
|
+
--card-elevation: 0;
|
|
22
22
|
|
|
23
|
-
// Typography
|
|
23
|
+
// Typography - use mixin
|
|
24
24
|
@include m.typography('body-medium');
|
|
25
25
|
|
|
26
|
-
// Transition for elevation and hover states
|
|
26
|
+
// Transition for elevation and hover states - use motion-transition mixin
|
|
27
27
|
@include m.motion-transition(
|
|
28
28
|
box-shadow,
|
|
29
29
|
background-color,
|
|
30
30
|
border-color
|
|
31
31
|
);
|
|
32
32
|
|
|
33
|
+
// Focus outline for accessibility - use focus-ring mixin
|
|
34
|
+
&:focus-visible {
|
|
35
|
+
@include m.focus-ring(t.color('secondary'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Focus state class
|
|
39
|
+
&--focused {
|
|
40
|
+
@include m.focus-ring(t.color('secondary'));
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
// Ripple styles for clickable cards
|
|
34
44
|
.ripple {
|
|
35
45
|
position: absolute;
|
|
@@ -37,7 +47,7 @@ $component: '#{base.$prefix}-card';
|
|
|
37
47
|
transform: scale(0);
|
|
38
48
|
pointer-events: none;
|
|
39
49
|
background-color: currentColor;
|
|
40
|
-
opacity:
|
|
50
|
+
opacity: f.get-state-opacity('hover'); // Use function for standard opacity
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
// Ensure proper stacking for inner components
|
|
@@ -45,35 +55,50 @@ $component: '#{base.$prefix}-card';
|
|
|
45
55
|
margin-bottom: 0;
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
// === Variants ===
|
|
58
|
+
// === Variants - use proper theme colors ===
|
|
49
59
|
|
|
50
60
|
// Elevated variant
|
|
51
61
|
&--elevated {
|
|
52
|
-
|
|
62
|
+
background-color: t.color('surface-container-low');
|
|
63
|
+
@include m.elevation(1); // Use elevation mixin
|
|
53
64
|
|
|
54
65
|
&:hover.#{$component}--interactive {
|
|
55
|
-
@include m.elevation(2);
|
|
66
|
+
@include m.elevation(2); // Use elevation mixin for hover state
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&:active.#{$component}--interactive {
|
|
70
|
+
@include m.state-layer(t.color('on-surface'), 'pressed'); // Use state-layer mixin
|
|
56
71
|
}
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
// Filled variant
|
|
60
75
|
&--filled {
|
|
61
76
|
background-color: t.color('surface-container-highest');
|
|
77
|
+
@include m.elevation(0); // No elevation
|
|
62
78
|
|
|
63
79
|
&:hover.#{$component}--interactive {
|
|
64
80
|
@include m.state-layer(t.color('on-surface'), 'hover');
|
|
65
81
|
}
|
|
82
|
+
|
|
83
|
+
&:active.#{$component}--interactive {
|
|
84
|
+
@include m.state-layer(t.color('on-surface'), 'pressed');
|
|
85
|
+
}
|
|
66
86
|
}
|
|
67
87
|
|
|
68
88
|
// Outlined variant
|
|
69
89
|
&--outlined {
|
|
70
90
|
border: 1px solid t.color('outline');
|
|
71
91
|
background-color: t.color('surface');
|
|
92
|
+
@include m.elevation(0); // No elevation
|
|
72
93
|
|
|
73
94
|
&:hover.#{$component}--interactive {
|
|
74
95
|
@include m.state-layer(t.color('on-surface'), 'hover');
|
|
75
96
|
border-color: t.color('outline-variant');
|
|
76
97
|
}
|
|
98
|
+
|
|
99
|
+
&:active.#{$component}--interactive {
|
|
100
|
+
@include m.state-layer(t.color('on-surface'), 'pressed');
|
|
101
|
+
}
|
|
77
102
|
}
|
|
78
103
|
|
|
79
104
|
// === Modifiers ===
|
|
@@ -95,10 +120,11 @@ $component: '#{base.$prefix}-card';
|
|
|
95
120
|
&-header {
|
|
96
121
|
display: flex;
|
|
97
122
|
align-items: center;
|
|
98
|
-
padding:
|
|
99
|
-
|
|
123
|
+
padding: v.card('padding');
|
|
124
|
+
padding-bottom: 0;
|
|
125
|
+
|
|
100
126
|
&-avatar {
|
|
101
|
-
margin-right:
|
|
127
|
+
margin-right: v.card('padding');
|
|
102
128
|
flex-shrink: 0;
|
|
103
129
|
display: flex;
|
|
104
130
|
align-items: center;
|
|
@@ -119,7 +145,7 @@ $component: '#{base.$prefix}-card';
|
|
|
119
145
|
|
|
120
146
|
&-title {
|
|
121
147
|
margin: 0;
|
|
122
|
-
@include m.typography('title-
|
|
148
|
+
@include m.typography('title-large');
|
|
123
149
|
@include m.truncate;
|
|
124
150
|
color: t.color('on-surface');
|
|
125
151
|
}
|
|
@@ -141,7 +167,7 @@ $component: '#{base.$prefix}-card';
|
|
|
141
167
|
&-media {
|
|
142
168
|
position: relative;
|
|
143
169
|
overflow: hidden;
|
|
144
|
-
|
|
170
|
+
border-radius: f.get-shape('medium');
|
|
145
171
|
&-img {
|
|
146
172
|
display: block;
|
|
147
173
|
width: 100%;
|
|
@@ -182,7 +208,7 @@ $component: '#{base.$prefix}-card';
|
|
|
182
208
|
|
|
183
209
|
// Card Content
|
|
184
210
|
&-content {
|
|
185
|
-
padding:
|
|
211
|
+
padding: v.card('padding');
|
|
186
212
|
flex: 1 1 auto;
|
|
187
213
|
|
|
188
214
|
> *:first-child {
|
|
@@ -195,7 +221,7 @@ $component: '#{base.$prefix}-card';
|
|
|
195
221
|
|
|
196
222
|
// When content follows media without padding
|
|
197
223
|
.#{$component}-media + &:not(.#{$component}-content--no-padding) {
|
|
198
|
-
padding-top:
|
|
224
|
+
padding-top: v.card('padding');
|
|
199
225
|
}
|
|
200
226
|
|
|
201
227
|
// No padding modifier
|
|
@@ -276,7 +302,7 @@ $component: '#{base.$prefix}-card';
|
|
|
276
302
|
|
|
277
303
|
// State classes
|
|
278
304
|
&--state-disabled {
|
|
279
|
-
opacity: 0.38;
|
|
305
|
+
opacity: 0.38; // Use MTRL standard opacity
|
|
280
306
|
pointer-events: none;
|
|
281
307
|
}
|
|
282
308
|
|
|
@@ -285,7 +311,7 @@ $component: '#{base.$prefix}-card';
|
|
|
285
311
|
}
|
|
286
312
|
|
|
287
313
|
&--dragging {
|
|
288
|
-
@include m.elevation(4);
|
|
314
|
+
@include m.elevation(4); // Use elevation mixin
|
|
289
315
|
opacity: 0.9;
|
|
290
316
|
}
|
|
291
317
|
|
|
@@ -299,15 +325,15 @@ $component: '#{base.$prefix}-card';
|
|
|
299
325
|
display: flex;
|
|
300
326
|
align-items: center;
|
|
301
327
|
justify-content: center;
|
|
302
|
-
background-color:
|
|
303
|
-
z-index:
|
|
328
|
+
background-color: t.alpha('surface', 0.7);
|
|
329
|
+
z-index: f.get-z-index('default');
|
|
304
330
|
}
|
|
305
331
|
|
|
306
332
|
&-loading-spinner {
|
|
307
333
|
width: 40px;
|
|
308
334
|
height: 40px;
|
|
309
335
|
border-radius: 50%;
|
|
310
|
-
border: 3px solid
|
|
336
|
+
border: 3px solid t.alpha('primary', 0.2);
|
|
311
337
|
border-top-color: t.color('primary');
|
|
312
338
|
animation: card-spinner 1s infinite linear;
|
|
313
339
|
}
|
|
@@ -315,7 +341,7 @@ $component: '#{base.$prefix}-card';
|
|
|
315
341
|
// Expandable content
|
|
316
342
|
&-expandable-content {
|
|
317
343
|
overflow: hidden;
|
|
318
|
-
transition: max-height
|
|
344
|
+
transition: max-height f.get-motion-duration('medium1') f.get-motion-easing('standard');
|
|
319
345
|
}
|
|
320
346
|
|
|
321
347
|
&-expand-button {
|
|
@@ -324,6 +350,9 @@ $component: '#{base.$prefix}-card';
|
|
|
324
350
|
padding: 8px;
|
|
325
351
|
cursor: pointer;
|
|
326
352
|
color: t.color('primary');
|
|
353
|
+
display: inline-flex;
|
|
354
|
+
align-items: center;
|
|
355
|
+
justify-content: center;
|
|
327
356
|
|
|
328
357
|
&::before {
|
|
329
358
|
content: '';
|
|
@@ -333,7 +362,7 @@ $component: '#{base.$prefix}-card';
|
|
|
333
362
|
border-right: 2px solid currentColor;
|
|
334
363
|
border-bottom: 2px solid currentColor;
|
|
335
364
|
transform: rotate(45deg);
|
|
336
|
-
transition: transform
|
|
365
|
+
transition: transform f.get-motion-duration('short2') f.get-motion-easing('standard');
|
|
337
366
|
}
|
|
338
367
|
|
|
339
368
|
&[aria-expanded="true"]::before {
|
|
@@ -344,7 +373,13 @@ $component: '#{base.$prefix}-card';
|
|
|
344
373
|
// Swipeable card
|
|
345
374
|
&--swipeable {
|
|
346
375
|
touch-action: pan-y;
|
|
347
|
-
transition: transform
|
|
376
|
+
transition: transform f.get-motion-duration('medium1') f.get-motion-easing('standard');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Hidden buttons for accessibility
|
|
380
|
+
&-swipe-left-action,
|
|
381
|
+
&-swipe-right-action {
|
|
382
|
+
@include m.visually-hidden; // Use visually-hidden mixin
|
|
348
383
|
}
|
|
349
384
|
}
|
|
350
385
|
|
|
@@ -356,4 +391,11 @@ $component: '#{base.$prefix}-card';
|
|
|
356
391
|
100% {
|
|
357
392
|
transform: rotate(360deg);
|
|
358
393
|
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Media query for responsive adjustments
|
|
397
|
+
@include m.breakpoint-down('sm') {
|
|
398
|
+
.#{$component} {
|
|
399
|
+
width: 100%; // Full width on small screens
|
|
400
|
+
}
|
|
359
401
|
}
|
|
@@ -3,6 +3,7 @@ import { BaseComponent, CardComponent, ApiOptions } from './types';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Enhances a card component with API methods
|
|
6
|
+
*
|
|
6
7
|
* @param {ApiOptions} options - API configuration options
|
|
7
8
|
* @returns {Function} Higher-order function that adds API methods to component
|
|
8
9
|
* @internal This is an internal utility for the Card component
|
|
@@ -13,6 +14,7 @@ export const withAPI = ({ lifecycle }: ApiOptions) => (component: BaseComponent)
|
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Adds content to the card
|
|
17
|
+
*
|
|
16
18
|
* @param {HTMLElement} contentElement - The content element to add
|
|
17
19
|
* @returns {CardComponent} The card instance for chaining
|
|
18
20
|
*/
|
|
@@ -25,8 +27,18 @@ export const withAPI = ({ lifecycle }: ApiOptions) => (component: BaseComponent)
|
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Sets the card header
|
|
30
|
+
*
|
|
31
|
+
* Places the header element in the card. When media elements exist,
|
|
32
|
+
* the header is placed after the last media element to ensure proper
|
|
33
|
+
* visual hierarchy following Material Design guidelines.
|
|
34
|
+
*
|
|
28
35
|
* @param {HTMLElement} headerElement - The header element to add
|
|
29
36
|
* @returns {CardComponent} The card instance for chaining
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Add a header after media
|
|
40
|
+
* card.setHeader(headerElement);
|
|
41
|
+
* ```
|
|
30
42
|
*/
|
|
31
43
|
setHeader(headerElement: HTMLElement): CardComponent {
|
|
32
44
|
if (headerElement && headerElement.classList.contains(`${component.getClass('card')}-header`)) {
|
|
@@ -36,14 +48,32 @@ export const withAPI = ({ lifecycle }: ApiOptions) => (component: BaseComponent)
|
|
|
36
48
|
existingHeader.remove();
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
//
|
|
40
|
-
component.element.
|
|
51
|
+
// Look for media element
|
|
52
|
+
const mediaElement = component.element.querySelector(`.${component.getClass('card')}-media`);
|
|
53
|
+
|
|
54
|
+
if (mediaElement) {
|
|
55
|
+
// If media exists, insert after the LAST media element
|
|
56
|
+
// Find all media elements
|
|
57
|
+
const mediaElements = component.element.querySelectorAll(`.${component.getClass('card')}-media`);
|
|
58
|
+
const lastMedia = mediaElements[mediaElements.length - 1];
|
|
59
|
+
|
|
60
|
+
// Insert after the last media element
|
|
61
|
+
if (lastMedia.nextSibling) {
|
|
62
|
+
component.element.insertBefore(headerElement, lastMedia.nextSibling);
|
|
63
|
+
} else {
|
|
64
|
+
component.element.appendChild(headerElement);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// No media, insert at the beginning
|
|
68
|
+
component.element.insertBefore(headerElement, component.element.firstChild);
|
|
69
|
+
}
|
|
41
70
|
}
|
|
42
71
|
return this;
|
|
43
72
|
},
|
|
44
73
|
|
|
45
74
|
/**
|
|
46
75
|
* Adds media to the card
|
|
76
|
+
*
|
|
47
77
|
* @param {HTMLElement} mediaElement - The media element to add
|
|
48
78
|
* @param {string} [position='top'] - Position to place media ('top', 'bottom')
|
|
49
79
|
* @returns {CardComponent} The card instance for chaining
|
|
@@ -61,6 +91,7 @@ export const withAPI = ({ lifecycle }: ApiOptions) => (component: BaseComponent)
|
|
|
61
91
|
|
|
62
92
|
/**
|
|
63
93
|
* Sets the card actions section
|
|
94
|
+
*
|
|
64
95
|
* @param {HTMLElement} actionsElement - The actions element to add
|
|
65
96
|
* @returns {CardComponent} The card instance for chaining
|
|
66
97
|
*/
|
|
@@ -80,19 +111,39 @@ export const withAPI = ({ lifecycle }: ApiOptions) => (component: BaseComponent)
|
|
|
80
111
|
|
|
81
112
|
/**
|
|
82
113
|
* Makes the card draggable
|
|
114
|
+
*
|
|
83
115
|
* @param {Function} [dragStartCallback] - Callback for drag start event
|
|
84
116
|
* @returns {CardComponent} The card instance for chaining
|
|
85
117
|
*/
|
|
86
118
|
makeDraggable(dragStartCallback?: (event: DragEvent) => void): CardComponent {
|
|
87
119
|
component.element.setAttribute('draggable', 'true');
|
|
120
|
+
component.element.setAttribute('aria-grabbed', 'false');
|
|
88
121
|
|
|
89
122
|
if (typeof dragStartCallback === 'function') {
|
|
90
|
-
component.element.addEventListener('dragstart',
|
|
123
|
+
component.element.addEventListener('dragstart', (e: DragEvent) => {
|
|
124
|
+
component.element.setAttribute('aria-grabbed', 'true');
|
|
125
|
+
dragStartCallback(e);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
component.element.addEventListener('dragend', () => {
|
|
129
|
+
component.element.setAttribute('aria-grabbed', 'false');
|
|
130
|
+
});
|
|
91
131
|
}
|
|
92
132
|
|
|
93
133
|
return this;
|
|
94
134
|
},
|
|
95
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Sets focus to the card
|
|
138
|
+
* Useful for programmatic focus management
|
|
139
|
+
*
|
|
140
|
+
* @returns {CardComponent} The card instance for chaining
|
|
141
|
+
*/
|
|
142
|
+
focus(): CardComponent {
|
|
143
|
+
component.element.focus();
|
|
144
|
+
return this;
|
|
145
|
+
},
|
|
146
|
+
|
|
96
147
|
/**
|
|
97
148
|
* Destroys the card component and removes event listeners
|
|
98
149
|
*/
|
|
@@ -9,12 +9,42 @@ import {
|
|
|
9
9
|
} from '../../core/compose/features';
|
|
10
10
|
import { withAPI } from './api';
|
|
11
11
|
import { CardComponent, BaseComponent, CardSchema } from './types';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
createBaseConfig,
|
|
14
|
+
getElementConfig,
|
|
15
|
+
getApiConfig,
|
|
16
|
+
withInteractiveBehavior
|
|
17
|
+
} from './config';
|
|
18
|
+
import { withElevation } from './features';
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* Creates a new Card component following Material Design 3 principles
|
|
22
|
+
*
|
|
23
|
+
* Material Design 3 Cards are surfaces that display content and actions about a single topic.
|
|
24
|
+
* Cards can contain text, media, and UI controls.
|
|
25
|
+
*
|
|
16
26
|
* @param {CardSchema} config - Card configuration object
|
|
17
27
|
* @returns {CardComponent} Card component instance
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Create a basic elevated card
|
|
32
|
+
* const card = createCard();
|
|
33
|
+
*
|
|
34
|
+
* // Create a filled card with content
|
|
35
|
+
* const filledCard = createCard({
|
|
36
|
+
* variant: CardVariant.FILLED,
|
|
37
|
+
* contentConfig: { text: 'Card content' }
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // Create an interactive outlined card
|
|
41
|
+
* const interactiveCard = createCard({
|
|
42
|
+
* variant: CardVariant.OUTLINED,
|
|
43
|
+
* interactive: true,
|
|
44
|
+
* clickable: true,
|
|
45
|
+
* aria: { label: 'Click to view details' }
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
18
48
|
*/
|
|
19
49
|
const createCard = (config: CardSchema = {}): CardComponent => {
|
|
20
50
|
const baseConfig = createBaseConfig(config);
|
|
@@ -28,9 +58,10 @@ const createCard = (config: CardSchema = {}): CardComponent => {
|
|
|
28
58
|
config.clickable ? withRipple(baseConfig) : (c: BaseComponent) => c,
|
|
29
59
|
withLifecycle(),
|
|
30
60
|
withInteractiveBehavior,
|
|
61
|
+
withElevation,
|
|
31
62
|
comp => withAPI(getApiConfig(comp))(comp)
|
|
32
63
|
)(baseConfig);
|
|
33
|
-
|
|
64
|
+
|
|
34
65
|
return card as CardComponent;
|
|
35
66
|
} catch (error) {
|
|
36
67
|
console.error('Card creation error:', error instanceof Error ? error.message : String(error));
|