mtrl 0.2.4 → 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.
Files changed (76) hide show
  1. package/package.json +6 -3
  2. package/src/components/badge/_styles.scss +9 -9
  3. package/src/components/button/_styles.scss +0 -56
  4. package/src/components/button/button.ts +0 -2
  5. package/src/components/button/constants.ts +0 -6
  6. package/src/components/button/index.ts +2 -2
  7. package/src/components/button/types.ts +1 -7
  8. package/src/components/card/_styles.scss +67 -25
  9. package/src/components/card/api.ts +54 -3
  10. package/src/components/card/card.ts +33 -2
  11. package/src/components/card/config.ts +143 -21
  12. package/src/components/card/constants.ts +20 -19
  13. package/src/components/card/content.ts +299 -2
  14. package/src/components/card/features.ts +155 -4
  15. package/src/components/card/index.ts +31 -9
  16. package/src/components/card/types.ts +138 -15
  17. package/src/components/chip/chip.ts +1 -9
  18. package/src/components/chip/constants.ts +0 -10
  19. package/src/components/chip/index.ts +1 -1
  20. package/src/components/chip/types.ts +1 -4
  21. package/src/components/progress/_styles.scss +0 -65
  22. package/src/components/progress/config.ts +1 -2
  23. package/src/components/progress/constants.ts +0 -14
  24. package/src/components/progress/index.ts +1 -1
  25. package/src/components/progress/progress.ts +1 -4
  26. package/src/components/progress/types.ts +1 -4
  27. package/src/components/radios/_styles.scss +0 -45
  28. package/src/components/radios/api.ts +85 -60
  29. package/src/components/radios/config.ts +1 -2
  30. package/src/components/radios/constants.ts +0 -9
  31. package/src/components/radios/index.ts +1 -1
  32. package/src/components/radios/radio.ts +34 -11
  33. package/src/components/radios/radios.ts +2 -1
  34. package/src/components/radios/types.ts +1 -7
  35. package/src/components/slider/_styles.scss +193 -281
  36. package/src/components/slider/accessibility.md +59 -0
  37. package/src/components/slider/api.ts +36 -101
  38. package/src/components/slider/config.ts +29 -78
  39. package/src/components/slider/constants.ts +12 -8
  40. package/src/components/slider/features/appearance.ts +1 -47
  41. package/src/components/slider/features/disabled.ts +41 -16
  42. package/src/components/slider/features/interactions.ts +166 -26
  43. package/src/components/slider/features/keyboard.ts +125 -6
  44. package/src/components/slider/features/structure.ts +182 -195
  45. package/src/components/slider/features/ui.ts +234 -303
  46. package/src/components/slider/index.ts +11 -1
  47. package/src/components/slider/slider.ts +1 -1
  48. package/src/components/slider/types.ts +10 -25
  49. package/src/components/tabs/_styles.scss +285 -155
  50. package/src/components/tabs/api.ts +178 -400
  51. package/src/components/tabs/config.ts +46 -52
  52. package/src/components/tabs/constants.ts +85 -8
  53. package/src/components/tabs/features.ts +401 -0
  54. package/src/components/tabs/index.ts +60 -3
  55. package/src/components/tabs/indicator.ts +225 -0
  56. package/src/components/tabs/responsive.ts +144 -0
  57. package/src/components/tabs/scroll-indicators.ts +149 -0
  58. package/src/components/tabs/state.ts +186 -0
  59. package/src/components/tabs/tab-api.ts +258 -0
  60. package/src/components/tabs/tab.ts +255 -0
  61. package/src/components/tabs/tabs.ts +50 -31
  62. package/src/components/tabs/types.ts +324 -128
  63. package/src/components/tabs/utils.ts +107 -0
  64. package/src/components/textfield/_styles.scss +0 -98
  65. package/src/components/textfield/config.ts +2 -3
  66. package/src/components/textfield/constants.ts +0 -14
  67. package/src/components/textfield/index.ts +2 -2
  68. package/src/components/textfield/textfield.ts +0 -2
  69. package/src/components/textfield/types.ts +1 -4
  70. package/src/core/compose/component.ts +1 -1
  71. package/src/core/compose/features/badge.ts +79 -0
  72. package/src/core/compose/features/index.ts +3 -1
  73. package/src/styles/abstract/_theme.scss +106 -2
  74. package/src/components/card/actions.ts +0 -48
  75. package/src/components/card/header.ts +0 -88
  76. package/src/components/card/media.ts +0 -52
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "mtrl",
3
- "version": "0.2.4",
4
- "description": "A functional JavaScript component library with composable architecture based on Material Design 3",
3
+ "version": "0.2.6",
4
+ "description": "A functional TypeScript/JavaScript component library with composable architecture based on Material Design 3",
5
5
  "keywords": [
6
6
  "component",
7
7
  "library",
8
8
  "ui",
9
9
  "user interface",
10
+ "typescript",
10
11
  "functional",
11
- "composable"
12
+ "composable",
13
+ "material design 3",
14
+ "md3"
12
15
  ],
13
16
  "main": "index.js",
14
17
  "scripts": {
@@ -112,18 +112,18 @@ $component: '#{base.$prefix}-badge';
112
112
  }
113
113
 
114
114
  &--success {
115
- background-color: t.color('success', t.color('primary'));
116
- color: t.color('on-success', t.color('on-primary'));
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', t.color('tertiary'));
121
- color: t.color('on-warning', t.color('on-tertiary'));
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', t.color('secondary'));
126
- color: t.color('on-info', t.color('on-secondary'));
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', t.color('primary'));
151
+ color: t.color('success');
152
152
  }
153
153
 
154
154
  &.#{$component}--warning {
155
- color: t.color('warning', t.color('tertiary'));
155
+ color: t.color('warning');
156
156
  }
157
157
 
158
158
  &.#{$component}--info {
159
- color: t.color('info', t.color('secondary'));
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),
@@ -8,10 +8,4 @@ export const BUTTON_VARIANTS = {
8
8
  OUTLINED: 'outlined',
9
9
  ELEVATED: 'elevated',
10
10
  TEXT: 'text'
11
- }
12
-
13
- export const BUTTON_SIZES = {
14
- SMALL: 'small',
15
- MEDIUM: 'medium',
16
- LARGE: 'large'
17
11
  }
@@ -1,4 +1,4 @@
1
1
  // src/components/button/index.ts
2
2
  export { default } from './button'
3
- export { BUTTON_VARIANTS, BUTTON_SIZES } from './constants'
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, BUTTON_SIZES } from './constants';
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/_card.scss
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: v.shape('medium');
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: 320px; // Fixed width since v.card() isn't available
21
- --card-elevation: 1;
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: 0.08;
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
- @include m.elevation(1);
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: 16px;
99
-
123
+ padding: v.card('padding');
124
+ padding-bottom: 0;
125
+
100
126
  &-avatar {
101
- margin-right: 16px;
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-medium');
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: 16px;
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: 16px;
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: rgba(t.color('surface'), 0.7);
303
- z-index: 1;
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 rgba(t.color('primary'), 0.2);
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 0.3s ease;
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 0.3s ease;
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 0.3s ease;
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
- // Insert at the beginning of the card
40
- component.element.insertBefore(headerElement, component.element.firstChild);
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', dragStartCallback as EventListener);
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 { createBaseConfig, getElementConfig, getApiConfig, withInteractiveBehavior } from './config';
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));