mtrl 0.1.2 → 0.2.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 (220) hide show
  1. package/README.md +70 -22
  2. package/index.ts +33 -0
  3. package/package.json +14 -5
  4. package/src/components/button/{styles.scss → _styles.scss} +2 -2
  5. package/src/components/button/api.ts +89 -0
  6. package/src/components/button/button.ts +50 -0
  7. package/src/components/button/config.ts +75 -0
  8. package/src/components/button/constants.ts +17 -0
  9. package/src/components/button/index.ts +4 -0
  10. package/src/components/button/types.ts +118 -0
  11. package/src/components/card/_styles.scss +359 -0
  12. package/src/components/card/actions.ts +48 -0
  13. package/src/components/card/api.ts +102 -0
  14. package/src/components/card/card.ts +41 -0
  15. package/src/components/card/config.ts +99 -0
  16. package/src/components/card/constants.ts +69 -0
  17. package/src/components/card/content.ts +48 -0
  18. package/src/components/card/features.ts +228 -0
  19. package/src/components/card/header.ts +88 -0
  20. package/src/components/card/index.ts +19 -0
  21. package/src/components/card/media.ts +52 -0
  22. package/src/components/card/types.ts +174 -0
  23. package/src/components/checkbox/api.ts +82 -0
  24. package/src/components/checkbox/checkbox.ts +75 -0
  25. package/src/components/checkbox/config.ts +90 -0
  26. package/src/components/checkbox/index.ts +4 -0
  27. package/src/components/checkbox/types.ts +146 -0
  28. package/src/components/chip/_styles.scss +372 -0
  29. package/src/components/chip/api.ts +115 -0
  30. package/src/components/chip/chip-set.ts +225 -0
  31. package/src/components/chip/chip.ts +82 -0
  32. package/src/components/chip/config.ts +92 -0
  33. package/src/components/chip/constants.ts +38 -0
  34. package/src/components/chip/index.ts +4 -0
  35. package/src/components/chip/types.ts +172 -0
  36. package/src/components/list/api.ts +72 -0
  37. package/src/components/list/config.ts +43 -0
  38. package/src/components/list/{constants.js → constants.ts} +34 -7
  39. package/src/components/list/features.ts +224 -0
  40. package/src/components/list/index.ts +14 -0
  41. package/src/components/list/list-item.ts +120 -0
  42. package/src/components/list/list.ts +37 -0
  43. package/src/components/list/types.ts +179 -0
  44. package/src/components/list/utils.ts +47 -0
  45. package/src/components/menu/api.ts +119 -0
  46. package/src/components/menu/config.ts +54 -0
  47. package/src/components/menu/constants.ts +154 -0
  48. package/src/components/menu/features/items-manager.ts +457 -0
  49. package/src/components/menu/features/keyboard-navigation.ts +133 -0
  50. package/src/components/menu/features/positioning.ts +127 -0
  51. package/src/components/menu/features/{visibility.js → visibility.ts} +66 -64
  52. package/src/components/menu/index.ts +14 -0
  53. package/src/components/menu/menu-item.ts +43 -0
  54. package/src/components/menu/menu.ts +53 -0
  55. package/src/components/menu/types.ts +178 -0
  56. package/src/components/navigation/api.ts +79 -0
  57. package/src/components/navigation/config.ts +61 -0
  58. package/src/components/navigation/{constants.js → constants.ts} +10 -10
  59. package/src/components/navigation/index.ts +14 -0
  60. package/src/components/navigation/nav-item.ts +148 -0
  61. package/src/components/navigation/navigation.ts +50 -0
  62. package/src/components/navigation/types.ts +212 -0
  63. package/src/components/progress/_styles.scss +204 -0
  64. package/src/components/progress/api.ts +179 -0
  65. package/src/components/progress/config.ts +124 -0
  66. package/src/components/progress/constants.ts +43 -0
  67. package/src/components/progress/index.ts +5 -0
  68. package/src/components/progress/progress.ts +163 -0
  69. package/src/components/progress/types.ts +102 -0
  70. package/src/components/snackbar/api.ts +162 -0
  71. package/src/components/snackbar/config.ts +62 -0
  72. package/src/components/snackbar/{constants.js → constants.ts} +21 -4
  73. package/src/components/snackbar/features.ts +76 -0
  74. package/src/components/snackbar/index.ts +4 -0
  75. package/src/components/snackbar/position.ts +71 -0
  76. package/src/components/snackbar/queue.ts +76 -0
  77. package/src/components/snackbar/snackbar.ts +60 -0
  78. package/src/components/snackbar/types.ts +58 -0
  79. package/src/components/switch/api.ts +77 -0
  80. package/src/components/switch/config.ts +74 -0
  81. package/src/components/switch/index.ts +4 -0
  82. package/src/components/switch/switch.ts +52 -0
  83. package/src/components/switch/types.ts +142 -0
  84. package/src/components/textfield/api.ts +72 -0
  85. package/src/components/textfield/config.ts +54 -0
  86. package/src/components/textfield/{constants.js → constants.ts} +38 -5
  87. package/src/components/textfield/index.ts +4 -0
  88. package/src/components/textfield/textfield.ts +50 -0
  89. package/src/components/textfield/types.ts +139 -0
  90. package/src/core/compose/base.ts +43 -0
  91. package/src/core/compose/component.ts +247 -0
  92. package/src/core/compose/features/checkable.ts +155 -0
  93. package/src/core/compose/features/disabled.ts +116 -0
  94. package/src/core/compose/features/events.ts +65 -0
  95. package/src/core/compose/features/icon.ts +67 -0
  96. package/src/core/compose/features/index.ts +35 -0
  97. package/src/core/compose/features/input.ts +174 -0
  98. package/src/core/compose/features/lifecycle.ts +139 -0
  99. package/src/core/compose/features/position.ts +94 -0
  100. package/src/core/compose/features/ripple.ts +55 -0
  101. package/src/core/compose/features/size.ts +29 -0
  102. package/src/core/compose/features/style.ts +31 -0
  103. package/src/core/compose/features/text.ts +44 -0
  104. package/src/core/compose/features/textinput.ts +225 -0
  105. package/src/core/compose/features/textlabel.ts +92 -0
  106. package/src/core/compose/features/track.ts +84 -0
  107. package/src/core/compose/features/variant.ts +29 -0
  108. package/src/core/compose/features/withEvents.ts +137 -0
  109. package/src/core/compose/index.ts +54 -0
  110. package/src/core/compose/{pipe.js → pipe.ts} +16 -11
  111. package/src/core/config/component-config.ts +136 -0
  112. package/src/core/config.ts +211 -0
  113. package/src/core/dom/{attributes.js → attributes.ts} +11 -11
  114. package/src/core/dom/classes.ts +60 -0
  115. package/src/core/dom/create.ts +188 -0
  116. package/src/core/dom/events.ts +209 -0
  117. package/src/core/dom/index.ts +10 -0
  118. package/src/core/dom/utils.ts +97 -0
  119. package/src/core/index.ts +111 -0
  120. package/src/core/state/disabled.ts +81 -0
  121. package/src/core/state/emitter.ts +94 -0
  122. package/src/core/state/events.ts +88 -0
  123. package/src/core/state/index.ts +16 -0
  124. package/src/core/state/lifecycle.ts +131 -0
  125. package/src/core/state/store.ts +197 -0
  126. package/src/core/utils/index.ts +45 -0
  127. package/src/core/utils/{mobile.js → mobile.ts} +48 -24
  128. package/src/core/utils/object.ts +41 -0
  129. package/src/core/utils/validate.ts +234 -0
  130. package/src/{index.js → index.ts} +4 -2
  131. package/index.js +0 -11
  132. package/src/components/button/api.js +0 -54
  133. package/src/components/button/button.js +0 -81
  134. package/src/components/button/config.js +0 -10
  135. package/src/components/button/constants.js +0 -63
  136. package/src/components/button/index.js +0 -2
  137. package/src/components/checkbox/api.js +0 -45
  138. package/src/components/checkbox/checkbox.js +0 -96
  139. package/src/components/checkbox/index.js +0 -2
  140. package/src/components/container/api.js +0 -42
  141. package/src/components/container/container.js +0 -45
  142. package/src/components/container/index.js +0 -2
  143. package/src/components/container/styles.scss +0 -66
  144. package/src/components/list/index.js +0 -2
  145. package/src/components/list/list-item.js +0 -147
  146. package/src/components/list/list.js +0 -267
  147. package/src/components/menu/api.js +0 -117
  148. package/src/components/menu/constants.js +0 -42
  149. package/src/components/menu/features/items-manager.js +0 -375
  150. package/src/components/menu/features/keyboard-navigation.js +0 -129
  151. package/src/components/menu/features/positioning.js +0 -125
  152. package/src/components/menu/index.js +0 -2
  153. package/src/components/menu/menu-item.js +0 -41
  154. package/src/components/menu/menu.js +0 -54
  155. package/src/components/navigation/api.js +0 -43
  156. package/src/components/navigation/index.js +0 -2
  157. package/src/components/navigation/nav-item.js +0 -137
  158. package/src/components/navigation/navigation.js +0 -55
  159. package/src/components/snackbar/api.js +0 -125
  160. package/src/components/snackbar/features.js +0 -69
  161. package/src/components/snackbar/index.js +0 -2
  162. package/src/components/snackbar/position.js +0 -63
  163. package/src/components/snackbar/queue.js +0 -74
  164. package/src/components/snackbar/snackbar.js +0 -70
  165. package/src/components/switch/api.js +0 -44
  166. package/src/components/switch/index.js +0 -2
  167. package/src/components/switch/switch.js +0 -71
  168. package/src/components/textfield/api.js +0 -49
  169. package/src/components/textfield/index.js +0 -2
  170. package/src/components/textfield/textfield.js +0 -68
  171. package/src/core/build/_ripple.scss +0 -79
  172. package/src/core/build/constants.js +0 -51
  173. package/src/core/build/icon.js +0 -78
  174. package/src/core/build/ripple.js +0 -159
  175. package/src/core/build/text.js +0 -54
  176. package/src/core/compose/base.js +0 -8
  177. package/src/core/compose/component.js +0 -225
  178. package/src/core/compose/features/checkable.js +0 -114
  179. package/src/core/compose/features/disabled.js +0 -64
  180. package/src/core/compose/features/events.js +0 -48
  181. package/src/core/compose/features/icon.js +0 -33
  182. package/src/core/compose/features/index.js +0 -20
  183. package/src/core/compose/features/input.js +0 -100
  184. package/src/core/compose/features/lifecycle.js +0 -69
  185. package/src/core/compose/features/position.js +0 -60
  186. package/src/core/compose/features/ripple.js +0 -32
  187. package/src/core/compose/features/size.js +0 -9
  188. package/src/core/compose/features/style.js +0 -12
  189. package/src/core/compose/features/text.js +0 -17
  190. package/src/core/compose/features/textinput.js +0 -114
  191. package/src/core/compose/features/textlabel.js +0 -28
  192. package/src/core/compose/features/track.js +0 -49
  193. package/src/core/compose/features/variant.js +0 -9
  194. package/src/core/compose/features/withEvents.js +0 -67
  195. package/src/core/compose/index.js +0 -16
  196. package/src/core/config.js +0 -140
  197. package/src/core/dom/classes.js +0 -70
  198. package/src/core/dom/create.js +0 -132
  199. package/src/core/dom/events.js +0 -175
  200. package/src/core/dom/index.js +0 -5
  201. package/src/core/dom/utils.js +0 -22
  202. package/src/core/index.js +0 -23
  203. package/src/core/state/disabled.js +0 -51
  204. package/src/core/state/emitter.js +0 -63
  205. package/src/core/state/events.js +0 -29
  206. package/src/core/state/index.js +0 -6
  207. package/src/core/state/lifecycle.js +0 -64
  208. package/src/core/state/store.js +0 -112
  209. package/src/core/utils/index.js +0 -39
  210. package/src/core/utils/object.js +0 -22
  211. package/src/core/utils/validate.js +0 -37
  212. /package/src/components/checkbox/{styles.scss → _styles.scss} +0 -0
  213. /package/src/components/checkbox/{constants.js → constants.ts} +0 -0
  214. /package/src/components/list/{styles.scss → _styles.scss} +0 -0
  215. /package/src/components/menu/{styles.scss → _styles.scss} +0 -0
  216. /package/src/components/navigation/{styles.scss → _styles.scss} +0 -0
  217. /package/src/components/snackbar/{styles.scss → _styles.scss} +0 -0
  218. /package/src/components/switch/{styles.scss → _styles.scss} +0 -0
  219. /package/src/components/switch/{constants.js → constants.ts} +0 -0
  220. /package/src/components/textfield/{styles.scss → _styles.scss} +0 -0
@@ -0,0 +1,359 @@
1
+ // src/components/card/_card.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}-card';
9
+
10
+ .#{$component} {
11
+ // Base styles
12
+ position: relative;
13
+ display: flex;
14
+ flex-direction: column;
15
+ box-sizing: border-box;
16
+ border-radius: v.shape('medium');
17
+ background-color: t.color('surface');
18
+ color: t.color('on-surface');
19
+ overflow: hidden;
20
+ width: 320px; // Fixed width since v.card() isn't available
21
+ --card-elevation: 1;
22
+
23
+ // Typography
24
+ @include m.typography('body-medium');
25
+
26
+ // Transition for elevation and hover states
27
+ @include m.motion-transition(
28
+ box-shadow,
29
+ background-color,
30
+ border-color
31
+ );
32
+
33
+ // Ripple styles for clickable cards
34
+ .ripple {
35
+ position: absolute;
36
+ border-radius: 50%;
37
+ transform: scale(0);
38
+ pointer-events: none;
39
+ background-color: currentColor;
40
+ opacity: 0.08;
41
+ }
42
+
43
+ // Ensure proper stacking for inner components
44
+ > :not(:last-child) {
45
+ margin-bottom: 0;
46
+ }
47
+
48
+ // === Variants ===
49
+
50
+ // Elevated variant
51
+ &--elevated {
52
+ @include m.elevation(1);
53
+
54
+ &:hover.#{$component}--interactive {
55
+ @include m.elevation(2);
56
+ }
57
+ }
58
+
59
+ // Filled variant
60
+ &--filled {
61
+ background-color: t.color('surface-container-highest');
62
+
63
+ &:hover.#{$component}--interactive {
64
+ @include m.state-layer(t.color('on-surface'), 'hover');
65
+ }
66
+ }
67
+
68
+ // Outlined variant
69
+ &--outlined {
70
+ border: 1px solid t.color('outline');
71
+ background-color: t.color('surface');
72
+
73
+ &:hover.#{$component}--interactive {
74
+ @include m.state-layer(t.color('on-surface'), 'hover');
75
+ border-color: t.color('outline-variant');
76
+ }
77
+ }
78
+
79
+ // === Modifiers ===
80
+
81
+ // Interactive cards
82
+ &--interactive {
83
+ cursor: pointer;
84
+ user-select: none;
85
+ }
86
+
87
+ // Full-width cards
88
+ &--full-width {
89
+ width: 100%;
90
+ }
91
+
92
+ // === Sub-components ===
93
+
94
+ // Card Header
95
+ &-header {
96
+ display: flex;
97
+ align-items: center;
98
+ padding: 16px;
99
+
100
+ &-avatar {
101
+ margin-right: 16px;
102
+ flex-shrink: 0;
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+
107
+ img {
108
+ width: 40px;
109
+ height: 40px;
110
+ border-radius: 50%;
111
+ object-fit: cover;
112
+ }
113
+ }
114
+
115
+ &-text {
116
+ flex: 1;
117
+ overflow: hidden;
118
+ }
119
+
120
+ &-title {
121
+ margin: 0;
122
+ @include m.typography('title-medium');
123
+ @include m.truncate;
124
+ color: t.color('on-surface');
125
+ }
126
+
127
+ &-subtitle {
128
+ margin: 0;
129
+ @include m.typography('body-medium');
130
+ @include m.truncate;
131
+ color: t.color('on-surface-variant');
132
+ }
133
+
134
+ &-action {
135
+ margin-left: 8px;
136
+ flex-shrink: 0;
137
+ }
138
+ }
139
+
140
+ // Card Media
141
+ &-media {
142
+ position: relative;
143
+ overflow: hidden;
144
+
145
+ &-img {
146
+ display: block;
147
+ width: 100%;
148
+ object-fit: cover;
149
+ }
150
+
151
+ // Aspect ratios
152
+ &--16-9 {
153
+ aspect-ratio: 16 / 9;
154
+
155
+ img {
156
+ height: 100%;
157
+ }
158
+ }
159
+
160
+ &--4-3 {
161
+ aspect-ratio: 4 / 3;
162
+
163
+ img {
164
+ height: 100%;
165
+ }
166
+ }
167
+
168
+ &--1-1 {
169
+ aspect-ratio: 1 / 1;
170
+
171
+ img {
172
+ height: 100%;
173
+ }
174
+ }
175
+
176
+ &--contain {
177
+ img {
178
+ object-fit: contain;
179
+ }
180
+ }
181
+ }
182
+
183
+ // Card Content
184
+ &-content {
185
+ padding: 16px;
186
+ flex: 1 1 auto;
187
+
188
+ > *:first-child {
189
+ margin-top: 0;
190
+ }
191
+
192
+ > *:last-child {
193
+ margin-bottom: 0;
194
+ }
195
+
196
+ // When content follows media without padding
197
+ .#{$component}-media + &:not(.#{$component}-content--no-padding) {
198
+ padding-top: 16px;
199
+ }
200
+
201
+ // No padding modifier
202
+ &--no-padding {
203
+ padding: 0;
204
+ }
205
+ }
206
+
207
+ // Card Actions
208
+ &-actions {
209
+ display: flex;
210
+ flex-wrap: wrap;
211
+ padding: 8px;
212
+ align-items: center;
213
+
214
+ > * {
215
+ margin: 0 4px;
216
+
217
+ &:first-child {
218
+ margin-left: 8px;
219
+ }
220
+
221
+ &:last-child {
222
+ margin-right: 8px;
223
+ }
224
+ }
225
+
226
+ // Full-bleed actions
227
+ &--full-bleed {
228
+ padding: 0;
229
+
230
+ > * {
231
+ margin: 0;
232
+ border-radius: 0;
233
+ flex: 1 1 auto;
234
+
235
+ &:first-child {
236
+ margin-left: 0;
237
+ }
238
+
239
+ &:last-child {
240
+ margin-right: 0;
241
+ }
242
+ }
243
+ }
244
+
245
+ // Vertical actions
246
+ &--vertical {
247
+ flex-direction: column;
248
+
249
+ > * {
250
+ width: 100%;
251
+ margin: 4px 0;
252
+
253
+ &:first-child {
254
+ margin-top: 8px;
255
+ }
256
+
257
+ &:last-child {
258
+ margin-bottom: 8px;
259
+ }
260
+ }
261
+ }
262
+
263
+ // Alignment variations
264
+ &--center {
265
+ justify-content: center;
266
+ }
267
+
268
+ &--end {
269
+ justify-content: flex-end;
270
+ }
271
+
272
+ &--space-between {
273
+ justify-content: space-between;
274
+ }
275
+ }
276
+
277
+ // State classes
278
+ &--state-disabled {
279
+ opacity: 0.38;
280
+ pointer-events: none;
281
+ }
282
+
283
+ &--state-loading {
284
+ pointer-events: none;
285
+ }
286
+
287
+ &--dragging {
288
+ @include m.elevation(4);
289
+ opacity: 0.9;
290
+ }
291
+
292
+ // Loading overlay
293
+ &-loading-overlay {
294
+ position: absolute;
295
+ top: 0;
296
+ left: 0;
297
+ width: 100%;
298
+ height: 100%;
299
+ display: flex;
300
+ align-items: center;
301
+ justify-content: center;
302
+ background-color: rgba(t.color('surface'), 0.7);
303
+ z-index: 1;
304
+ }
305
+
306
+ &-loading-spinner {
307
+ width: 40px;
308
+ height: 40px;
309
+ border-radius: 50%;
310
+ border: 3px solid rgba(t.color('primary'), 0.2);
311
+ border-top-color: t.color('primary');
312
+ animation: card-spinner 1s infinite linear;
313
+ }
314
+
315
+ // Expandable content
316
+ &-expandable-content {
317
+ overflow: hidden;
318
+ transition: max-height 0.3s ease;
319
+ }
320
+
321
+ &-expand-button {
322
+ background: none;
323
+ border: none;
324
+ padding: 8px;
325
+ cursor: pointer;
326
+ color: t.color('primary');
327
+
328
+ &::before {
329
+ content: '';
330
+ display: inline-block;
331
+ width: 10px;
332
+ height: 10px;
333
+ border-right: 2px solid currentColor;
334
+ border-bottom: 2px solid currentColor;
335
+ transform: rotate(45deg);
336
+ transition: transform 0.3s ease;
337
+ }
338
+
339
+ &[aria-expanded="true"]::before {
340
+ transform: rotate(-135deg);
341
+ }
342
+ }
343
+
344
+ // Swipeable card
345
+ &--swipeable {
346
+ touch-action: pan-y;
347
+ transition: transform 0.3s ease;
348
+ }
349
+ }
350
+
351
+ // Spinner animation
352
+ @keyframes card-spinner {
353
+ 0% {
354
+ transform: rotate(0deg);
355
+ }
356
+ 100% {
357
+ transform: rotate(360deg);
358
+ }
359
+ }
@@ -0,0 +1,48 @@
1
+ // src/components/card/actions.ts
2
+ import { PREFIX } from '../../core/config';
3
+ import { pipe } from '../../core/compose';
4
+ import { createBase, withElement } from '../../core/compose/component';
5
+ import { CardActionsConfig } from './types';
6
+
7
+ /**
8
+ * Creates a card actions component
9
+ * @param {CardActionsConfig} config - Actions configuration
10
+ * @returns {HTMLElement} Card actions element
11
+ */
12
+ export const createCardActions = (config: CardActionsConfig = {}): HTMLElement => {
13
+ const baseConfig = {
14
+ ...config,
15
+ componentName: 'card-actions',
16
+ prefix: PREFIX
17
+ };
18
+
19
+ try {
20
+ const actions = pipe(
21
+ createBase,
22
+ withElement({
23
+ tag: 'div',
24
+ componentName: 'card-actions',
25
+ className: [
26
+ config.class,
27
+ config.fullBleed ? `${PREFIX}-card-actions--full-bleed` : null,
28
+ config.vertical ? `${PREFIX}-card-actions--vertical` : null,
29
+ config.align ? `${PREFIX}-card-actions--${config.align}` : null
30
+ ]
31
+ })
32
+ )(baseConfig);
33
+
34
+ // Add action elements if provided
35
+ if (Array.isArray(config.actions)) {
36
+ config.actions.forEach(action => {
37
+ if (action instanceof HTMLElement) {
38
+ actions.element.appendChild(action);
39
+ }
40
+ });
41
+ }
42
+
43
+ return actions.element;
44
+ } catch (error) {
45
+ console.error('Card actions creation error:', error instanceof Error ? error.message : String(error));
46
+ throw new Error(`Failed to create card actions: ${error instanceof Error ? error.message : String(error)}`);
47
+ }
48
+ };
@@ -0,0 +1,102 @@
1
+ // src/components/card/api.ts
2
+ import { BaseComponent, CardComponent, ApiOptions } from './types';
3
+
4
+ /**
5
+ * Enhances a card component with API methods
6
+ * @param {ApiOptions} options - API configuration options
7
+ * @returns {Function} Higher-order function that adds API methods to component
8
+ * @internal This is an internal utility for the Card component
9
+ */
10
+ export const withAPI = ({ lifecycle }: ApiOptions) => (component: BaseComponent): CardComponent => ({
11
+ ...component,
12
+ element: component.element,
13
+
14
+ /**
15
+ * Adds content to the card
16
+ * @param {HTMLElement} contentElement - The content element to add
17
+ * @returns {CardComponent} The card instance for chaining
18
+ */
19
+ addContent(contentElement: HTMLElement): CardComponent {
20
+ if (contentElement && contentElement.classList.contains(`${component.getClass('card')}-content`)) {
21
+ component.element.appendChild(contentElement);
22
+ }
23
+ return this;
24
+ },
25
+
26
+ /**
27
+ * Sets the card header
28
+ * @param {HTMLElement} headerElement - The header element to add
29
+ * @returns {CardComponent} The card instance for chaining
30
+ */
31
+ setHeader(headerElement: HTMLElement): CardComponent {
32
+ if (headerElement && headerElement.classList.contains(`${component.getClass('card')}-header`)) {
33
+ // Remove existing header if present
34
+ const existingHeader = component.element.querySelector(`.${component.getClass('card')}-header`);
35
+ if (existingHeader) {
36
+ existingHeader.remove();
37
+ }
38
+
39
+ // Insert at the beginning of the card
40
+ component.element.insertBefore(headerElement, component.element.firstChild);
41
+ }
42
+ return this;
43
+ },
44
+
45
+ /**
46
+ * Adds media to the card
47
+ * @param {HTMLElement} mediaElement - The media element to add
48
+ * @param {string} [position='top'] - Position to place media ('top', 'bottom')
49
+ * @returns {CardComponent} The card instance for chaining
50
+ */
51
+ addMedia(mediaElement: HTMLElement, position: 'top' | 'bottom' = 'top'): CardComponent {
52
+ if (mediaElement && mediaElement.classList.contains(`${component.getClass('card')}-media`)) {
53
+ if (position === 'top') {
54
+ component.element.insertBefore(mediaElement, component.element.firstChild);
55
+ } else {
56
+ component.element.appendChild(mediaElement);
57
+ }
58
+ }
59
+ return this;
60
+ },
61
+
62
+ /**
63
+ * Sets the card actions section
64
+ * @param {HTMLElement} actionsElement - The actions element to add
65
+ * @returns {CardComponent} The card instance for chaining
66
+ */
67
+ setActions(actionsElement: HTMLElement): CardComponent {
68
+ if (actionsElement && actionsElement.classList.contains(`${component.getClass('card')}-actions`)) {
69
+ // Remove existing actions if present
70
+ const existingActions = component.element.querySelector(`.${component.getClass('card')}-actions`);
71
+ if (existingActions) {
72
+ existingActions.remove();
73
+ }
74
+
75
+ // Add actions at the end
76
+ component.element.appendChild(actionsElement);
77
+ }
78
+ return this;
79
+ },
80
+
81
+ /**
82
+ * Makes the card draggable
83
+ * @param {Function} [dragStartCallback] - Callback for drag start event
84
+ * @returns {CardComponent} The card instance for chaining
85
+ */
86
+ makeDraggable(dragStartCallback?: (event: DragEvent) => void): CardComponent {
87
+ component.element.setAttribute('draggable', 'true');
88
+
89
+ if (typeof dragStartCallback === 'function') {
90
+ component.element.addEventListener('dragstart', dragStartCallback as EventListener);
91
+ }
92
+
93
+ return this;
94
+ },
95
+
96
+ /**
97
+ * Destroys the card component and removes event listeners
98
+ */
99
+ destroy(): void {
100
+ lifecycle.destroy();
101
+ }
102
+ });
@@ -0,0 +1,41 @@
1
+ // src/components/card/card.ts
2
+ import { pipe } from '../../core/compose';
3
+ import { createBase, withElement } from '../../core/compose/component';
4
+ import {
5
+ withEvents,
6
+ withVariant,
7
+ withRipple,
8
+ withLifecycle
9
+ } from '../../core/compose/features';
10
+ import { withAPI } from './api';
11
+ import { CardComponent, BaseComponent, CardSchema } from './types';
12
+ import { createBaseConfig, getElementConfig, getApiConfig, withInteractiveBehavior } from './config';
13
+
14
+ /**
15
+ * Creates a new Card component following Material Design 3 principles
16
+ * @param {CardSchema} config - Card configuration object
17
+ * @returns {CardComponent} Card component instance
18
+ */
19
+ const createCard = (config: CardSchema = {}): CardComponent => {
20
+ const baseConfig = createBaseConfig(config);
21
+
22
+ try {
23
+ const card = pipe(
24
+ createBase,
25
+ withEvents(),
26
+ withElement(getElementConfig(baseConfig)),
27
+ withVariant(baseConfig),
28
+ config.clickable ? withRipple(baseConfig) : (c: BaseComponent) => c,
29
+ withLifecycle(),
30
+ withInteractiveBehavior,
31
+ comp => withAPI(getApiConfig(comp))(comp)
32
+ )(baseConfig);
33
+
34
+ return card as CardComponent;
35
+ } catch (error) {
36
+ console.error('Card creation error:', error instanceof Error ? error.message : String(error));
37
+ throw new Error(`Failed to create card: ${error instanceof Error ? error.message : String(error)}`);
38
+ }
39
+ };
40
+
41
+ export default createCard;
@@ -0,0 +1,99 @@
1
+ // src/components/card/config.ts
2
+ import {
3
+ createComponentConfig,
4
+ createElementConfig,
5
+ BaseComponentConfig
6
+ } from '../../core/config/component-config';
7
+ import { BaseComponent, CardSchema } from './types';
8
+ import { CARD_VARIANTS, CARD_ELEVATIONS } from './constants';
9
+
10
+ /**
11
+ * Default configuration for the Card component
12
+ */
13
+ export const defaultConfig: CardSchema = {
14
+ variant: CARD_VARIANTS.ELEVATED,
15
+ interactive: false,
16
+ fullWidth: false,
17
+ clickable: false,
18
+ draggable: false
19
+ };
20
+
21
+ /**
22
+ * Creates the base configuration for Card component
23
+ * @param {CardSchema} config - User provided configuration
24
+ * @returns {CardSchema} Complete configuration with defaults applied
25
+ */
26
+ export const createBaseConfig = (config: CardSchema = {}): CardSchema =>
27
+ createComponentConfig(defaultConfig, config, 'card') as CardSchema;
28
+
29
+ /**
30
+ * Generates element configuration for the Card component
31
+ * @param {CardSchema} config - Card configuration
32
+ * @returns {Object} Element configuration object for withElement
33
+ */
34
+ export const getElementConfig = (config: CardSchema) =>
35
+ createElementConfig(config, {
36
+ tag: 'div',
37
+ className: [
38
+ config.class,
39
+ config.fullWidth ? `${config.prefix}-card--full-width` : null,
40
+ config.interactive ? `${config.prefix}-card--interactive` : null
41
+ ],
42
+ forwardEvents: {
43
+ click: (component: BaseComponent) => !!config.clickable,
44
+ mouseenter: (component: BaseComponent) => !!config.interactive,
45
+ mouseleave: (component: BaseComponent) => !!config.interactive
46
+ },
47
+ interactive: config.interactive || config.clickable
48
+ });
49
+
50
+ /**
51
+ * Creates API configuration for the Card component
52
+ * @param {Object} comp - Component with lifecycle feature
53
+ * @returns {Object} API configuration object
54
+ */
55
+ export const getApiConfig = (comp) => ({
56
+ lifecycle: {
57
+ destroy: () => comp.lifecycle?.destroy?.()
58
+ }
59
+ });
60
+
61
+ /**
62
+ * Adds interactive behavior to card component
63
+ * @param {BaseComponent} comp - Card component
64
+ * @returns {BaseComponent} Enhanced card component
65
+ */
66
+ export const withInteractiveBehavior = (comp: BaseComponent): BaseComponent => {
67
+ // Implement hover state elevation changes for interactive cards
68
+ if (comp.config.interactive) {
69
+ comp.element.addEventListener('mouseenter', () => {
70
+ if (comp.config.variant === CARD_VARIANTS.ELEVATED) {
71
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.HOVERED));
72
+ }
73
+ });
74
+
75
+ comp.element.addEventListener('mouseleave', () => {
76
+ if (comp.config.variant === CARD_VARIANTS.ELEVATED) {
77
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.RESTING));
78
+ }
79
+ });
80
+ }
81
+
82
+ // Set up draggable
83
+ if (comp.config.draggable) {
84
+ comp.element.setAttribute('draggable', 'true');
85
+ comp.element.addEventListener('dragstart', (e) => {
86
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.DRAGGED));
87
+ comp.emit?.('dragstart', { event: e });
88
+ });
89
+
90
+ comp.element.addEventListener('dragend', (e) => {
91
+ comp.element.style.setProperty('--card-elevation', String(CARD_ELEVATIONS.RESTING));
92
+ comp.emit?.('dragend', { event: e });
93
+ });
94
+ }
95
+
96
+ return comp;
97
+ };
98
+
99
+ export default defaultConfig;
@@ -0,0 +1,69 @@
1
+ // src/components/card/constants.ts
2
+ import { CardVariant, CardElevation } from './types';
3
+
4
+ /**
5
+ * Card variant types following Material Design 3
6
+ * @enum {string}
7
+ */
8
+ export const CARD_VARIANTS = {
9
+ ELEVATED: CardVariant.ELEVATED,
10
+ FILLED: CardVariant.FILLED,
11
+ OUTLINED: CardVariant.OUTLINED
12
+ };
13
+
14
+ /**
15
+ * Card elevation levels
16
+ * @enum {number}
17
+ */
18
+ export const CARD_ELEVATIONS = {
19
+ RESTING: CardElevation.RESTING,
20
+ HOVERED: CardElevation.HOVERED,
21
+ DRAGGED: CardElevation.DRAGGED
22
+ };
23
+
24
+ /**
25
+ * Validation schema for card configuration
26
+ */
27
+ export const CARD_SCHEMA = {
28
+ variant: {
29
+ type: 'string',
30
+ enum: Object.values(CARD_VARIANTS),
31
+ default: CARD_VARIANTS.ELEVATED
32
+ },
33
+ interactive: {
34
+ type: 'boolean',
35
+ default: false
36
+ },
37
+ fullWidth: {
38
+ type: 'boolean',
39
+ default: false
40
+ },
41
+ clickable: {
42
+ type: 'boolean',
43
+ default: false
44
+ },
45
+ draggable: {
46
+ type: 'boolean',
47
+ default: false
48
+ },
49
+ class: {
50
+ type: 'string',
51
+ required: false
52
+ },
53
+ headerConfig: {
54
+ type: 'object',
55
+ required: false
56
+ },
57
+ contentConfig: {
58
+ type: 'object',
59
+ required: false
60
+ },
61
+ actionsConfig: {
62
+ type: 'object',
63
+ required: false
64
+ },
65
+ mediaConfig: {
66
+ type: 'object',
67
+ required: false
68
+ }
69
+ };