mtrl 0.2.5 → 0.2.7

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 (196) hide show
  1. package/index.ts +18 -0
  2. package/package.json +1 -1
  3. package/src/components/badge/_styles.scss +123 -115
  4. package/src/components/badge/api.ts +57 -59
  5. package/src/components/badge/badge.ts +16 -2
  6. package/src/components/badge/config.ts +65 -11
  7. package/src/components/badge/constants.ts +22 -12
  8. package/src/components/badge/features.ts +44 -40
  9. package/src/components/badge/types.ts +42 -30
  10. package/src/components/bottom-app-bar/_styles.scss +103 -0
  11. package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
  12. package/src/components/bottom-app-bar/config.ts +73 -0
  13. package/src/components/bottom-app-bar/index.ts +11 -0
  14. package/src/components/bottom-app-bar/types.ts +108 -0
  15. package/src/components/button/_styles.scss +0 -66
  16. package/src/components/button/api.ts +5 -0
  17. package/src/components/button/button.ts +0 -2
  18. package/src/components/button/config.ts +5 -0
  19. package/src/components/button/constants.ts +0 -6
  20. package/src/components/button/index.ts +2 -2
  21. package/src/components/button/types.ts +7 -7
  22. package/src/components/card/_styles.scss +67 -25
  23. package/src/components/card/api.ts +54 -3
  24. package/src/components/card/card.ts +25 -6
  25. package/src/components/card/config.ts +189 -22
  26. package/src/components/card/constants.ts +20 -19
  27. package/src/components/card/content.ts +299 -2
  28. package/src/components/card/features.ts +158 -4
  29. package/src/components/card/index.ts +31 -9
  30. package/src/components/card/types.ts +166 -15
  31. package/src/components/checkbox/_styles.scss +0 -2
  32. package/src/components/chip/chip.ts +1 -9
  33. package/src/components/chip/constants.ts +0 -10
  34. package/src/components/chip/index.ts +1 -1
  35. package/src/components/chip/types.ts +1 -4
  36. package/src/components/datepicker/_styles.scss +358 -0
  37. package/src/components/datepicker/api.ts +272 -0
  38. package/src/components/datepicker/config.ts +144 -0
  39. package/src/components/datepicker/constants.ts +98 -0
  40. package/src/components/datepicker/datepicker.ts +346 -0
  41. package/src/components/datepicker/index.ts +9 -0
  42. package/src/components/datepicker/render.ts +452 -0
  43. package/src/components/datepicker/types.ts +268 -0
  44. package/src/components/datepicker/utils.ts +290 -0
  45. package/src/components/dialog/_styles.scss +174 -128
  46. package/src/components/dialog/api.ts +48 -13
  47. package/src/components/dialog/config.ts +9 -5
  48. package/src/components/dialog/dialog.ts +6 -3
  49. package/src/components/dialog/features.ts +290 -130
  50. package/src/components/dialog/types.ts +7 -4
  51. package/src/components/divider/_styles.scss +57 -0
  52. package/src/components/divider/config.ts +81 -0
  53. package/src/components/divider/divider.ts +37 -0
  54. package/src/components/divider/features.ts +207 -0
  55. package/src/components/divider/index.ts +5 -0
  56. package/src/components/divider/types.ts +55 -0
  57. package/src/components/extended-fab/_styles.scss +267 -0
  58. package/src/components/extended-fab/api.ts +141 -0
  59. package/src/components/extended-fab/config.ts +108 -0
  60. package/src/components/extended-fab/constants.ts +36 -0
  61. package/src/components/extended-fab/extended-fab.ts +125 -0
  62. package/src/components/extended-fab/index.ts +4 -0
  63. package/src/components/extended-fab/types.ts +287 -0
  64. package/src/components/fab/_styles.scss +225 -0
  65. package/src/components/fab/api.ts +97 -0
  66. package/src/components/fab/config.ts +94 -0
  67. package/src/components/fab/constants.ts +41 -0
  68. package/src/components/fab/fab.ts +67 -0
  69. package/src/components/fab/index.ts +4 -0
  70. package/src/components/fab/types.ts +234 -0
  71. package/src/components/navigation/_styles.scss +1 -0
  72. package/src/components/navigation/api.ts +78 -50
  73. package/src/components/navigation/features/items.ts +280 -0
  74. package/src/components/navigation/nav-item.ts +72 -23
  75. package/src/components/navigation/navigation.ts +54 -2
  76. package/src/components/navigation/types.ts +210 -188
  77. package/src/components/progress/_styles.scss +0 -65
  78. package/src/components/progress/config.ts +1 -2
  79. package/src/components/progress/constants.ts +0 -14
  80. package/src/components/progress/index.ts +1 -1
  81. package/src/components/progress/progress.ts +1 -4
  82. package/src/components/progress/types.ts +1 -4
  83. package/src/components/radios/_styles.scss +0 -45
  84. package/src/components/radios/api.ts +85 -60
  85. package/src/components/radios/config.ts +1 -2
  86. package/src/components/radios/constants.ts +0 -9
  87. package/src/components/radios/index.ts +1 -1
  88. package/src/components/radios/radio.ts +34 -11
  89. package/src/components/radios/radios.ts +2 -1
  90. package/src/components/radios/types.ts +1 -7
  91. package/src/components/search/_styles.scss +306 -0
  92. package/src/components/search/api.ts +203 -0
  93. package/src/components/search/config.ts +87 -0
  94. package/src/components/search/constants.ts +21 -0
  95. package/src/components/search/features/index.ts +4 -0
  96. package/src/components/search/features/search.ts +718 -0
  97. package/src/components/search/features/states.ts +165 -0
  98. package/src/components/search/features/structure.ts +198 -0
  99. package/src/components/search/index.ts +10 -0
  100. package/src/components/search/search.ts +52 -0
  101. package/src/components/search/types.ts +163 -0
  102. package/src/components/segmented-button/_styles.scss +117 -0
  103. package/src/components/segmented-button/config.ts +67 -0
  104. package/src/components/segmented-button/constants.ts +42 -0
  105. package/src/components/segmented-button/index.ts +4 -0
  106. package/src/components/segmented-button/segment.ts +155 -0
  107. package/src/components/segmented-button/segmented-button.ts +250 -0
  108. package/src/components/segmented-button/types.ts +219 -0
  109. package/src/components/slider/_styles.scss +221 -168
  110. package/src/components/slider/accessibility.md +59 -0
  111. package/src/components/slider/api.ts +41 -120
  112. package/src/components/slider/config.ts +51 -49
  113. package/src/components/slider/features/handlers.ts +495 -0
  114. package/src/components/slider/features/index.ts +1 -2
  115. package/src/components/slider/features/slider.ts +66 -84
  116. package/src/components/slider/features/states.ts +195 -0
  117. package/src/components/slider/features/structure.ts +141 -184
  118. package/src/components/slider/features/ui.ts +150 -201
  119. package/src/components/slider/index.ts +2 -11
  120. package/src/components/slider/slider.ts +9 -12
  121. package/src/components/slider/types.ts +39 -24
  122. package/src/components/switch/_styles.scss +0 -2
  123. package/src/components/tabs/_styles.scss +346 -154
  124. package/src/components/tabs/api.ts +178 -400
  125. package/src/components/tabs/config.ts +46 -52
  126. package/src/components/tabs/constants.ts +85 -8
  127. package/src/components/tabs/features.ts +403 -0
  128. package/src/components/tabs/index.ts +60 -3
  129. package/src/components/tabs/indicator.ts +285 -0
  130. package/src/components/tabs/responsive.ts +144 -0
  131. package/src/components/tabs/scroll-indicators.ts +149 -0
  132. package/src/components/tabs/state.ts +186 -0
  133. package/src/components/tabs/tab-api.ts +258 -0
  134. package/src/components/tabs/tab.ts +255 -0
  135. package/src/components/tabs/tabs.ts +50 -31
  136. package/src/components/tabs/types.ts +332 -128
  137. package/src/components/tabs/utils.ts +107 -0
  138. package/src/components/textfield/_styles.scss +0 -98
  139. package/src/components/textfield/config.ts +2 -3
  140. package/src/components/textfield/constants.ts +0 -14
  141. package/src/components/textfield/index.ts +2 -2
  142. package/src/components/textfield/textfield.ts +0 -2
  143. package/src/components/textfield/types.ts +1 -4
  144. package/src/components/timepicker/README.md +277 -0
  145. package/src/components/timepicker/_styles.scss +451 -0
  146. package/src/components/timepicker/api.ts +632 -0
  147. package/src/components/timepicker/clockdial.ts +482 -0
  148. package/src/components/timepicker/config.ts +130 -0
  149. package/src/components/timepicker/constants.ts +138 -0
  150. package/src/components/timepicker/index.ts +8 -0
  151. package/src/components/timepicker/render.ts +613 -0
  152. package/src/components/timepicker/timepicker.ts +117 -0
  153. package/src/components/timepicker/types.ts +336 -0
  154. package/src/components/timepicker/utils.ts +241 -0
  155. package/src/components/top-app-bar/_styles.scss +225 -0
  156. package/src/components/top-app-bar/config.ts +83 -0
  157. package/src/components/top-app-bar/index.ts +11 -0
  158. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  159. package/src/components/top-app-bar/types.ts +140 -0
  160. package/src/core/build/_ripple.scss +6 -6
  161. package/src/core/build/ripple.ts +72 -95
  162. package/src/core/compose/component.ts +1 -1
  163. package/src/core/compose/features/badge.ts +79 -0
  164. package/src/core/compose/features/icon.ts +3 -1
  165. package/src/core/compose/features/index.ts +3 -1
  166. package/src/core/compose/features/ripple.ts +4 -1
  167. package/src/core/compose/features/textlabel.ts +26 -2
  168. package/src/core/dom/create.ts +5 -0
  169. package/src/index.ts +9 -0
  170. package/src/styles/abstract/_theme.scss +115 -3
  171. package/src/styles/themes/_autumn.scss +21 -0
  172. package/src/styles/themes/_base-theme.scss +61 -0
  173. package/src/styles/themes/_baseline.scss +58 -0
  174. package/src/styles/themes/_bluekhaki.scss +125 -0
  175. package/src/styles/themes/_brownbeige.scss +125 -0
  176. package/src/styles/themes/_browngreen.scss +125 -0
  177. package/src/styles/themes/_forest.scss +6 -0
  178. package/src/styles/themes/_greenbeige.scss +125 -0
  179. package/src/styles/themes/_material.scss +125 -0
  180. package/src/styles/themes/_ocean.scss +6 -0
  181. package/src/styles/themes/_sageivory.scss +125 -0
  182. package/src/styles/themes/_spring.scss +6 -0
  183. package/src/styles/themes/_summer.scss +5 -0
  184. package/src/styles/themes/_sunset.scss +5 -0
  185. package/src/styles/themes/_tealcaramel.scss +125 -0
  186. package/src/styles/themes/_winter.scss +6 -0
  187. package/src/components/card/actions.ts +0 -48
  188. package/src/components/card/header.ts +0 -88
  189. package/src/components/card/media.ts +0 -52
  190. package/src/components/navigation/features/items.js +0 -192
  191. package/src/components/slider/features/appearance.ts +0 -94
  192. package/src/components/slider/features/disabled.ts +0 -43
  193. package/src/components/slider/features/events.ts +0 -164
  194. package/src/components/slider/features/interactions.ts +0 -261
  195. package/src/components/slider/features/keyboard.ts +0 -112
  196. package/src/core/collection/adapters/mongodb.js +0 -232
@@ -1,14 +1,21 @@
1
1
  // src/components/card/config.ts
2
+
2
3
  import {
3
4
  createComponentConfig,
4
- createElementConfig,
5
- BaseComponentConfig
5
+ createElementConfig
6
6
  } from '../../core/config/component-config';
7
- import { BaseComponent, CardSchema } from './types';
8
7
  import { CARD_VARIANTS, CARD_ELEVATIONS } from './constants';
8
+ import {
9
+ createCardHeader,
10
+ createCardContent,
11
+ createCardMedia,
12
+ createCardActions
13
+ } from './content';
14
+ import { CardComponent, CardSchema, ButtonConfig, BaseComponent } from './types';
9
15
 
10
16
  /**
11
17
  * Default configuration for the Card component
18
+ * @const {CardSchema}
12
19
  */
13
20
  export const defaultConfig: CardSchema = {
14
21
  variant: CARD_VARIANTS.ELEVATED,
@@ -18,8 +25,101 @@ export const defaultConfig: CardSchema = {
18
25
  draggable: false
19
26
  };
20
27
 
28
+ /**
29
+ * Processes inline configuration options into standard config format
30
+ * Maps shorthand properties to their proper config counterparts
31
+ *
32
+ * @param {CardSchema} config - Raw card configuration
33
+ * @returns {CardSchema} Processed configuration
34
+ */
35
+ export const processInlineConfig = (config: CardSchema): CardSchema => {
36
+ const processedConfig: CardSchema = { ...config };
37
+
38
+ // Map inline properties to their *Config counterparts
39
+ if (config.header) {
40
+ processedConfig.headerConfig = config.header;
41
+ }
42
+
43
+ if (config.content) {
44
+ processedConfig.contentConfig = config.content;
45
+ }
46
+
47
+ if (config.media) {
48
+ processedConfig.mediaConfig = config.media;
49
+ }
50
+
51
+ if (config.actions) {
52
+ processedConfig.actionsConfig = config.actions;
53
+ }
54
+
55
+ return processedConfig;
56
+ };
57
+
58
+ /**
59
+ * Applies inline configuration to a card component
60
+ * Adds configured elements to the card in the correct order
61
+ *
62
+ * @param {CardComponent} card - Card component to configure
63
+ * @param {CardSchema} config - Processed configuration
64
+ */
65
+ export const applyInlineConfiguration = (card: CardComponent, config: CardSchema): void => {
66
+ // Add media (top position) if configured
67
+ if (config.mediaConfig && (!config.mediaConfig.position || config.mediaConfig.position === 'top')) {
68
+ const { position, ...mediaConfigWithoutPosition } = config.mediaConfig;
69
+ const mediaElement = createCardMedia(mediaConfigWithoutPosition);
70
+ card.addMedia(mediaElement, 'top');
71
+ }
72
+
73
+ // Add header if configured
74
+ if (config.headerConfig) {
75
+ const headerElement = createCardHeader(config.headerConfig);
76
+ card.setHeader(headerElement);
77
+ }
78
+
79
+ // Add content if configured
80
+ if (config.contentConfig) {
81
+ const contentElement = createCardContent(config.contentConfig);
82
+ card.addContent(contentElement);
83
+ }
84
+
85
+ // Add media (bottom position) if configured
86
+ if (config.mediaConfig && config.mediaConfig.position === 'bottom') {
87
+ const { position, ...mediaConfigWithoutPosition } = config.mediaConfig;
88
+ const mediaElement = createCardMedia(mediaConfigWithoutPosition);
89
+ card.addMedia(mediaElement, 'bottom');
90
+ }
91
+
92
+ // Add actions if configured
93
+ if (config.actionsConfig) {
94
+ const actionsElement = createCardActions(config.actionsConfig);
95
+ card.setActions(actionsElement);
96
+ }
97
+
98
+ // Process buttons if provided (asynchronously)
99
+ if (Array.isArray(config.buttons) && config.buttons.length > 0) {
100
+ import('../button').then(({ default: createButton }) => {
101
+ // Create buttons from configuration
102
+ const actionButtons = config.buttons!.map(buttonConfig =>
103
+ createButton(buttonConfig).element
104
+ );
105
+
106
+ // Create actions container
107
+ const actionsElement = createCardActions({
108
+ actions: actionButtons,
109
+ align: config.actionsConfig?.align || 'end'
110
+ });
111
+
112
+ // Add the actions to the card
113
+ card.setActions(actionsElement);
114
+ }).catch(error => {
115
+ console.error('Error processing buttons:', error);
116
+ });
117
+ }
118
+ };
119
+
21
120
  /**
22
121
  * Creates the base configuration for Card component
122
+ *
23
123
  * @param {CardSchema} config - User provided configuration
24
124
  * @returns {CardSchema} Complete configuration with defaults applied
25
125
  */
@@ -28,31 +128,64 @@ export const createBaseConfig = (config: CardSchema = {}): CardSchema =>
28
128
 
29
129
  /**
30
130
  * Generates element configuration for the Card component
131
+ *
31
132
  * @param {CardSchema} config - Card configuration
32
133
  * @returns {Object} Element configuration object for withElement
33
134
  */
34
- export const getElementConfig = (config: CardSchema) =>
35
- createElementConfig(config, {
135
+ export const getElementConfig = (config: CardSchema) => {
136
+ const isInteractive = config.interactive || config.clickable;
137
+ const defaultRole = isInteractive ? 'button' : 'region';
138
+
139
+ // Prepare ARIA attributes
140
+ const ariaAttrs: Record<string, string> = {};
141
+ if (config.aria) {
142
+ // Add all ARIA attributes from config
143
+ Object.entries(config.aria).forEach(([key, value]) => {
144
+ if (value !== undefined) {
145
+ // Convert attribute name to aria-* format if not already
146
+ const attrName = key.startsWith('aria-') ? key : `aria-${key}`;
147
+ ariaAttrs[attrName] = value;
148
+ }
149
+ });
150
+ }
151
+
152
+ // Set default ARIA role if not specified
153
+ if (!ariaAttrs['role'] && !config.aria?.role) {
154
+ ariaAttrs['role'] = defaultRole;
155
+ }
156
+
157
+ // Add tabindex for interactive cards if not specified
158
+ if (isInteractive && !ariaAttrs['tabindex']) {
159
+ ariaAttrs['tabindex'] = '0';
160
+ }
161
+
162
+ return createElementConfig(config, {
36
163
  tag: 'div',
37
164
  className: [
38
165
  config.class,
39
166
  config.fullWidth ? `${config.prefix}-card--full-width` : null,
40
- config.interactive ? `${config.prefix}-card--interactive` : null
167
+ isInteractive ? `${config.prefix}-card--interactive` : null
41
168
  ],
169
+ attrs: ariaAttrs,
42
170
  forwardEvents: {
43
171
  click: (component: BaseComponent) => !!config.clickable,
44
- mouseenter: (component: BaseComponent) => !!config.interactive,
45
- mouseleave: (component: BaseComponent) => !!config.interactive
172
+ mouseenter: (component: BaseComponent) => !!isInteractive,
173
+ mouseleave: (component: BaseComponent) => !!isInteractive,
174
+ keydown: (component: BaseComponent) => !!isInteractive,
175
+ focus: (component: BaseComponent) => !!isInteractive,
176
+ blur: (component: BaseComponent) => !!isInteractive
46
177
  },
47
- interactive: config.interactive || config.clickable
178
+ interactive: isInteractive
48
179
  });
180
+ };
49
181
 
50
182
  /**
51
183
  * Creates API configuration for the Card component
184
+ *
52
185
  * @param {Object} comp - Component with lifecycle feature
53
186
  * @returns {Object} API configuration object
54
187
  */
55
- export const getApiConfig = (comp) => ({
188
+ export const getApiConfig = (comp: any) => ({
56
189
  lifecycle: {
57
190
  destroy: () => comp.lifecycle?.destroy?.()
58
191
  }
@@ -60,35 +193,69 @@ export const getApiConfig = (comp) => ({
60
193
 
61
194
  /**
62
195
  * Adds interactive behavior to card component
196
+ * Uses the MTRL elevation system for proper elevation levels
197
+ *
63
198
  * @param {BaseComponent} comp - Card component
64
199
  * @returns {BaseComponent} Enhanced card component
65
200
  */
66
201
  export const withInteractiveBehavior = (comp: BaseComponent): BaseComponent => {
67
- // Implement hover state elevation changes for interactive cards
68
- if (comp.config.interactive) {
202
+ const config = comp.config;
203
+ const isInteractive = config.interactive || config.clickable;
204
+
205
+ // Implement MD3 elevation changes for interactive cards
206
+ if (isInteractive) {
207
+ // Mouse interactions
69
208
  comp.element.addEventListener('mouseenter', () => {
70
- if (comp.config.variant === CARD_VARIANTS.ELEVATED) {
71
- comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.HOVERED));
209
+ if (config.variant === CARD_VARIANTS.ELEVATED) {
210
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL2));
72
211
  }
73
212
  });
74
213
 
75
214
  comp.element.addEventListener('mouseleave', () => {
76
- if (comp.config.variant === CARD_VARIANTS.ELEVATED) {
77
- comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.RESTING));
215
+ if (config.variant === CARD_VARIANTS.ELEVATED) {
216
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL1));
217
+ }
218
+ });
219
+
220
+ // Keyboard interactions for accessibility
221
+ comp.element.addEventListener('keydown', (e: KeyboardEvent) => {
222
+ // Activate on Enter or Space
223
+ if ((e.key === 'Enter' || e.key === ' ') && config.clickable) {
224
+ e.preventDefault();
225
+ comp.element.click();
78
226
  }
79
227
  });
228
+
229
+ // Focus state handling
230
+ comp.element.addEventListener('focus', () => {
231
+ comp.element.classList.add(`${comp.getClass('card')}--focused`);
232
+ });
233
+
234
+ comp.element.addEventListener('blur', () => {
235
+ comp.element.classList.remove(`${comp.getClass('card')}--focused`);
236
+ });
80
237
  }
81
238
 
82
- // Set up draggable
83
- if (comp.config.draggable) {
239
+ // Set up draggable behavior
240
+ if (config.draggable) {
84
241
  comp.element.setAttribute('draggable', 'true');
85
- comp.element.addEventListener('dragstart', (e) => {
86
- comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.DRAGGED));
242
+
243
+ comp.element.addEventListener('dragstart', (e: DragEvent) => {
244
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL4));
245
+ comp.element.classList.add(`${comp.getClass('card')}--dragging`);
87
246
  comp.emit?.('dragstart', { event: e });
247
+
248
+ // Ensure keyboard users can see what's being dragged
249
+ if (e.dataTransfer) {
250
+ // Set drag image and data
251
+ const cardTitle = comp.element.querySelector(`.${comp.getClass('card')}-header-title`)?.textContent || 'Card';
252
+ e.dataTransfer.setData('text/plain', cardTitle);
253
+ }
88
254
  });
89
255
 
90
- comp.element.addEventListener('dragend', (e) => {
91
- comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.RESTING));
256
+ comp.element.addEventListener('dragend', (e: DragEvent) => {
257
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.LEVEL1));
258
+ comp.element.classList.remove(`${comp.getClass('card')}--dragging`);
92
259
  comp.emit?.('dragend', { event: e });
93
260
  });
94
261
  }
@@ -1,4 +1,5 @@
1
1
  // src/components/card/constants.ts
2
+
2
3
  import { CardVariant, CardElevation } from './types';
3
4
 
4
5
  /**
@@ -6,37 +7,33 @@ import { CardVariant, CardElevation } from './types';
6
7
  * @enum {string}
7
8
  */
8
9
  export const CARD_VARIANTS = {
10
+ /** Elevated card with shadow */
9
11
  ELEVATED: CardVariant.ELEVATED,
12
+ /** Filled card with higher surface container color */
10
13
  FILLED: CardVariant.FILLED,
14
+ /** Outlined card with border */
11
15
  OUTLINED: CardVariant.OUTLINED
12
16
  };
13
17
 
14
18
  /**
15
- * Card elevation levels
19
+ * Card elevation levels based on MD3 guidelines
20
+ * Uses the MTRL elevation system values
16
21
  * @enum {number}
17
22
  */
18
23
  export const CARD_ELEVATIONS = {
19
- RESTING: CardElevation.RESTING,
20
- HOVERED: CardElevation.HOVERED,
21
- DRAGGED: CardElevation.DRAGGED
24
+ /** No elevation (for filled and outlined variants) */
25
+ LEVEL0: CardElevation.LEVEL0,
26
+ /** Default elevation for elevated cards */
27
+ LEVEL1: CardElevation.LEVEL1,
28
+ /** Elevation for hovered state */
29
+ LEVEL2: CardElevation.LEVEL2,
30
+ /** Elevation for dragged state */
31
+ LEVEL4: CardElevation.LEVEL4
22
32
  };
23
33
 
24
- // Default width values following MD3 principles
25
- export const CARD_WIDTHS = {
26
- // Mobile-optimized default (MD3 recommends 344dp for small screens)
27
- DEFAULT: '344px',
28
- // Percentage-based responsive options
29
- FULL: '100%',
30
- HALF: '50%',
31
- // Fixed widths for different breakpoints
32
- SMALL: '344px',
33
- MEDIUM: '480px',
34
- LARGE: '624px'
35
- }
36
-
37
-
38
34
  /**
39
- * Validation schema for card configuration
35
+ * Card validation schema
36
+ * @const {Object}
40
37
  */
41
38
  export const CARD_SCHEMA = {
42
39
  variant: {
@@ -79,5 +76,9 @@ export const CARD_SCHEMA = {
79
76
  mediaConfig: {
80
77
  type: 'object',
81
78
  required: false
79
+ },
80
+ aria: {
81
+ type: 'object',
82
+ required: false
82
83
  }
83
84
  };
@@ -3,11 +3,25 @@ import { PREFIX } from '../../core/config';
3
3
  import { pipe } from '../../core/compose';
4
4
  import { createBase, withElement } from '../../core/compose/component';
5
5
  import { CardContentConfig } from './types';
6
+ import { CARD_CONTENT_PADDING } from './constants';
6
7
 
7
8
  /**
8
9
  * Creates a card content component
10
+ *
9
11
  * @param {CardContentConfig} config - Content configuration
10
12
  * @returns {HTMLElement} Card content element
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Create text content
17
+ * const textContent = createCardContent({ text: 'Simple text content' });
18
+ *
19
+ * // Create HTML content with no padding
20
+ * const htmlContent = createCardContent({
21
+ * html: '<p>Formatted <strong>HTML</strong> content</p>',
22
+ * padding: false
23
+ * });
24
+ * ```
11
25
  */
12
26
  export const createCardContent = (config: CardContentConfig = {}): HTMLElement => {
13
27
  const baseConfig = {
@@ -17,6 +31,8 @@ export const createCardContent = (config: CardContentConfig = {}): HTMLElement =
17
31
  };
18
32
 
19
33
  try {
34
+ // Create element with innerHTML instead of html/text properties
35
+ // for more reliable content rendering
20
36
  const content = pipe(
21
37
  createBase,
22
38
  withElement({
@@ -26,11 +42,22 @@ export const createCardContent = (config: CardContentConfig = {}): HTMLElement =
26
42
  config.class,
27
43
  config.padding === false ? `${PREFIX}-card-content--no-padding` : null
28
44
  ],
29
- html: config.html,
30
- text: config.text
45
+ attrs: {
46
+ 'role': 'region',
47
+ // Add explicit style attributes to ensure visibility
48
+ 'style': 'display: block; color: inherit;'
49
+ }
31
50
  })
32
51
  )(baseConfig);
33
52
 
53
+ // Explicitly set the innerHTML for more reliable rendering
54
+ if (config.html) {
55
+ content.element.innerHTML = config.html;
56
+ } else if (config.text) {
57
+ // Wrap text in paragraph for proper formatting
58
+ content.element.innerHTML = `<p>${config.text}</p>`;
59
+ }
60
+
34
61
  // Add children if provided
35
62
  if (Array.isArray(config.children)) {
36
63
  config.children.forEach(child => {
@@ -40,9 +67,279 @@ export const createCardContent = (config: CardContentConfig = {}): HTMLElement =
40
67
  });
41
68
  }
42
69
 
70
+ // Add debug class to make troubleshooting easier
71
+ // Remove this in production
72
+ content.element.classList.add('debug-content');
73
+
43
74
  return content.element;
44
75
  } catch (error) {
45
76
  console.error('Card content creation error:', error instanceof Error ? error.message : String(error));
46
77
  throw new Error(`Failed to create card content: ${error instanceof Error ? error.message : String(error)}`);
47
78
  }
79
+ };
80
+
81
+ // src/components/card/header.ts
82
+ import { createElement } from '../../core/dom/create';
83
+
84
+ /**
85
+ * Creates a card header component
86
+ *
87
+ * @param {CardHeaderConfig} config - Header configuration
88
+ * @returns {HTMLElement} Card header element
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * // Create a header with title and subtitle
93
+ * const header = createCardHeader({
94
+ * title: 'Card Title',
95
+ * subtitle: 'Supporting text'
96
+ * });
97
+ *
98
+ * // Create a header with an avatar and action
99
+ * const avatarHeader = createCardHeader({
100
+ * title: 'User Profile',
101
+ * avatar: '<img src="user.jpg" alt="User avatar">',
102
+ * action: createIconButton({ icon: 'more_vert' })
103
+ * });
104
+ * ```
105
+ */
106
+ export const createCardHeader = (config: any = {}): HTMLElement => {
107
+ const baseConfig = {
108
+ ...config,
109
+ componentName: 'card-header',
110
+ prefix: PREFIX
111
+ };
112
+
113
+ try {
114
+ const header = pipe(
115
+ createBase,
116
+ withElement({
117
+ tag: 'div',
118
+ componentName: 'card-header',
119
+ className: config.class,
120
+ attrs: {
121
+ 'role': 'heading',
122
+ 'aria-level': '3' // Default heading level
123
+ }
124
+ })
125
+ )(baseConfig);
126
+
127
+ // Create text container for title and subtitle
128
+ const textContainer = createElement({
129
+ tag: 'div',
130
+ className: `${PREFIX}-card-header-text`,
131
+ container: header.element
132
+ });
133
+
134
+ // Add title if provided
135
+ if (config.title) {
136
+ createElement({
137
+ tag: 'h3',
138
+ className: `${PREFIX}-card-header-title`,
139
+ text: config.title,
140
+ container: textContainer,
141
+ attrs: {
142
+ id: `${header.element.id || 'card-header'}-title`
143
+ }
144
+ });
145
+
146
+ // Link the title ID to the card for accessibility if parent card exists
147
+ const parentCard = header.element.closest(`.${PREFIX}-card`);
148
+ if (parentCard && !parentCard.hasAttribute('aria-labelledby')) {
149
+ parentCard.setAttribute('aria-labelledby', `${header.element.id || 'card-header'}-title`);
150
+ }
151
+ }
152
+
153
+ // Add subtitle if provided
154
+ if (config.subtitle) {
155
+ createElement({
156
+ tag: 'h4',
157
+ className: `${PREFIX}-card-header-subtitle`,
158
+ text: config.subtitle,
159
+ container: textContainer
160
+ });
161
+ }
162
+
163
+ // Add avatar if provided
164
+ if (config.avatar) {
165
+ const avatarElement = typeof config.avatar === 'string'
166
+ ? createElement({
167
+ tag: 'div',
168
+ className: `${PREFIX}-card-header-avatar`,
169
+ html: config.avatar
170
+ })
171
+ : config.avatar;
172
+
173
+ // Ensure avatar has correct ARIA attributes if it's an image
174
+ const avatarImg = avatarElement.querySelector('img');
175
+ if (avatarImg && !avatarImg.hasAttribute('alt')) {
176
+ avatarImg.setAttribute('alt', ''); // Decorative image
177
+ avatarImg.setAttribute('aria-hidden', 'true');
178
+ }
179
+
180
+ header.element.insertBefore(avatarElement, header.element.firstChild);
181
+ }
182
+
183
+ // Add action if provided
184
+ if (config.action) {
185
+ const actionElement = typeof config.action === 'string'
186
+ ? createElement({
187
+ tag: 'div',
188
+ className: `${PREFIX}-card-header-action`,
189
+ html: config.action
190
+ })
191
+ : config.action;
192
+
193
+ header.element.appendChild(actionElement);
194
+ }
195
+
196
+ return header.element;
197
+ } catch (error) {
198
+ console.error('Card header creation error:', error instanceof Error ? error.message : String(error));
199
+ throw new Error(`Failed to create card header: ${error instanceof Error ? error.message : String(error)}`);
200
+ }
201
+ };
202
+
203
+ // src/components/card/actions.ts
204
+ /**
205
+ * Creates a card actions component
206
+ *
207
+ * @param {CardActionsConfig} config - Actions configuration
208
+ * @returns {HTMLElement} Card actions element
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * // Create simple actions container with buttons
213
+ * const actions = createCardActions({
214
+ * actions: [
215
+ * createButton({ text: 'Cancel' }),
216
+ * createButton({ text: 'OK', variant: 'filled' })
217
+ * ],
218
+ * align: 'end'
219
+ * });
220
+ *
221
+ * // Create full-bleed actions
222
+ * const fullBleedActions = createCardActions({
223
+ * actions: [createButton({ text: 'View Details', fullWidth: true })],
224
+ * fullBleed: true
225
+ * });
226
+ * ```
227
+ */
228
+ export const createCardActions = (config: any = {}): HTMLElement => {
229
+ const baseConfig = {
230
+ ...config,
231
+ componentName: 'card-actions',
232
+ prefix: PREFIX
233
+ };
234
+
235
+ try {
236
+ const actions = pipe(
237
+ createBase,
238
+ withElement({
239
+ tag: 'div',
240
+ componentName: 'card-actions',
241
+ className: [
242
+ config.class,
243
+ config.fullBleed ? `${PREFIX}-card-actions--full-bleed` : null,
244
+ config.vertical ? `${PREFIX}-card-actions--vertical` : null,
245
+ config.align ? `${PREFIX}-card-actions--${config.align}` : null
246
+ ],
247
+ attrs: {
248
+ 'role': 'group' // Semantically group actions together
249
+ }
250
+ })
251
+ )(baseConfig);
252
+
253
+ // Add action elements if provided
254
+ if (Array.isArray(config.actions)) {
255
+ config.actions.forEach((action, index) => {
256
+ if (action instanceof HTMLElement) {
257
+ // Ensure each action has accessible attributes
258
+ if (!action.hasAttribute('aria-label') &&
259
+ !action.hasAttribute('aria-labelledby') &&
260
+ action.textContent?.trim() === '') {
261
+ action.setAttribute('aria-label', `Action ${index + 1}`);
262
+ }
263
+
264
+ actions.element.appendChild(action);
265
+ }
266
+ });
267
+ }
268
+
269
+ return actions.element;
270
+ } catch (error) {
271
+ console.error('Card actions creation error:', error instanceof Error ? error.message : String(error));
272
+ throw new Error(`Failed to create card actions: ${error instanceof Error ? error.message : String(error)}`);
273
+ }
274
+ };
275
+
276
+ // src/components/card/media.ts
277
+ /**
278
+ * Creates a card media component
279
+ *
280
+ * @param {CardMediaConfig} config - Media configuration
281
+ * @returns {HTMLElement} Card media element
282
+ *
283
+ * @example
284
+ * ```typescript
285
+ * // Create a media component with an image
286
+ * const media = createCardMedia({
287
+ * src: 'image.jpg',
288
+ * alt: 'Descriptive alt text',
289
+ * aspectRatio: '16:9'
290
+ * });
291
+ *
292
+ * // Create a media component with a custom element
293
+ * const customMedia = createCardMedia({
294
+ * element: videoElement,
295
+ * aspectRatio: '4:3'
296
+ * });
297
+ * ```
298
+ */
299
+ export const createCardMedia = (config: any = {}): HTMLElement => {
300
+ const baseConfig = {
301
+ ...config,
302
+ componentName: 'card-media',
303
+ prefix: PREFIX
304
+ };
305
+
306
+ try {
307
+ const media = pipe(
308
+ createBase,
309
+ withElement({
310
+ tag: 'div',
311
+ componentName: 'card-media',
312
+ className: [
313
+ config.class,
314
+ config.aspectRatio ? `${PREFIX}-card-media--${config.aspectRatio.replace(':', '-')}` : null,
315
+ config.contain ? `${PREFIX}-card-media--contain` : null
316
+ ]
317
+ })
318
+ )(baseConfig);
319
+
320
+ // If custom element is provided, use it
321
+ if (config.element instanceof HTMLElement) {
322
+ media.element.appendChild(config.element);
323
+ }
324
+ // Otherwise create an image if src is provided
325
+ else if (config.src) {
326
+ const img = document.createElement('img');
327
+ img.src = config.src;
328
+ img.className = `${PREFIX}-card-media-img`;
329
+
330
+ // Ensure alt text is always provided for accessibility
331
+ img.alt = config.alt || '';
332
+ if (!config.alt) {
333
+ // If no alt text is provided, mark as decorative
334
+ img.setAttribute('aria-hidden', 'true');
335
+ }
336
+
337
+ media.element.appendChild(img);
338
+ }
339
+
340
+ return media.element;
341
+ } catch (error) {
342
+ console.error('Card media creation error:', error instanceof Error ? error.message : String(error));
343
+ throw new Error(`Failed to create card media: ${error instanceof Error ? error.message : String(error)}`);
344
+ }
48
345
  };