mtrl 0.2.1 → 0.2.2
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/card/constants.ts +14 -0
- package/src/components/chip/_styles.scss +83 -140
- package/src/components/chip/api.ts +231 -102
- package/src/components/chip/chip.ts +356 -44
- package/src/components/chip/constants.ts +3 -3
- package/src/components/chip/index.ts +3 -3
- package/src/styles/abstract/_variables.scss +12 -0
package/package.json
CHANGED
|
@@ -21,6 +21,20 @@ export const CARD_ELEVATIONS = {
|
|
|
21
21
|
DRAGGED: CardElevation.DRAGGED
|
|
22
22
|
};
|
|
23
23
|
|
|
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
|
+
|
|
24
38
|
/**
|
|
25
39
|
* Validation schema for card configuration
|
|
26
40
|
*/
|
|
@@ -13,25 +13,21 @@ $component: '#{base.$prefix}-chip';
|
|
|
13
13
|
display: inline-flex;
|
|
14
14
|
align-items: center;
|
|
15
15
|
justify-content: center;
|
|
16
|
-
height:
|
|
17
|
-
padding: 0
|
|
18
|
-
border:
|
|
19
|
-
border-radius: 8px;
|
|
16
|
+
height: v.chip('height');
|
|
17
|
+
padding: 0 v.chip('padding-horizontal');
|
|
18
|
+
border-radius: v.chip('border-radius');
|
|
20
19
|
background-color: transparent;
|
|
21
|
-
|
|
22
|
-
font: inherit;
|
|
23
|
-
text-decoration: none;
|
|
24
|
-
cursor: pointer;
|
|
25
|
-
user-select: none;
|
|
26
|
-
vertical-align: middle;
|
|
27
|
-
appearance: none;
|
|
20
|
+
max-width: 100%;
|
|
28
21
|
overflow: hidden;
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
user-select: none;
|
|
23
|
+
cursor: pointer;
|
|
31
24
|
|
|
32
25
|
// Typography
|
|
33
26
|
@include m.typography('label-large');
|
|
34
27
|
|
|
28
|
+
// Interaction styles
|
|
29
|
+
@include m.motion-transition(background-color, color, border-color, box-shadow);
|
|
30
|
+
|
|
35
31
|
// Focus styles
|
|
36
32
|
&:focus {
|
|
37
33
|
outline: none;
|
|
@@ -43,50 +39,57 @@ $component: '#{base.$prefix}-chip';
|
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
// Disabled state
|
|
46
|
-
|
|
47
|
-
pointer-events: none;
|
|
42
|
+
&--disabled {
|
|
48
43
|
opacity: 0.38;
|
|
44
|
+
pointer-events: none;
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
//
|
|
52
|
-
&-
|
|
53
|
-
display:
|
|
47
|
+
// Content container
|
|
48
|
+
&-content {
|
|
49
|
+
display: flex;
|
|
54
50
|
align-items: center;
|
|
55
51
|
justify-content: center;
|
|
56
|
-
width:
|
|
57
|
-
height:
|
|
58
|
-
margin-right: 8px;
|
|
59
|
-
|
|
60
|
-
svg {
|
|
61
|
-
width: 18px;
|
|
62
|
-
height: 18px;
|
|
63
|
-
}
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
64
54
|
}
|
|
65
55
|
|
|
66
|
-
//
|
|
56
|
+
// Ensure proper layout with icons
|
|
57
|
+
&-leading-icon,
|
|
67
58
|
&-trailing-icon {
|
|
68
59
|
display: inline-flex;
|
|
69
60
|
align-items: center;
|
|
70
61
|
justify-content: center;
|
|
71
62
|
width: 18px;
|
|
72
63
|
height: 18px;
|
|
73
|
-
margin-left: 8px;
|
|
74
64
|
|
|
75
65
|
svg {
|
|
76
66
|
width: 18px;
|
|
77
67
|
height: 18px;
|
|
78
68
|
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&-leading-icon {
|
|
72
|
+
margin-right: 8px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&-trailing-icon {
|
|
76
|
+
margin-left: 8px;
|
|
79
77
|
|
|
80
78
|
&:hover {
|
|
81
|
-
opacity: 0.
|
|
79
|
+
opacity: 0.7;
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
// Text content
|
|
86
83
|
&-text {
|
|
87
84
|
// Text truncation for long chip labels
|
|
88
85
|
@include m.truncate;
|
|
89
|
-
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Selected state
|
|
89
|
+
&--selected {
|
|
90
|
+
// Default selected state styling (can be overridden by variants)
|
|
91
|
+
background-color: t.alpha('on-surface', 0.12);
|
|
92
|
+
font-weight: 500;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
// Ripple container
|
|
@@ -99,9 +102,7 @@ $component: '#{base.$prefix}-chip';
|
|
|
99
102
|
opacity: 0.12;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
// Filled chip (default)
|
|
105
|
+
// Variants
|
|
105
106
|
&--filled {
|
|
106
107
|
background-color: t.color('surface-container-highest');
|
|
107
108
|
color: t.color('on-surface');
|
|
@@ -128,7 +129,6 @@ $component: '#{base.$prefix}-chip';
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
// Outlined chip
|
|
132
132
|
&--outlined {
|
|
133
133
|
border: 1px solid t.color('outline');
|
|
134
134
|
color: t.color('on-surface');
|
|
@@ -142,21 +142,16 @@ $component: '#{base.$prefix}-chip';
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
&.#{$component}--selected {
|
|
145
|
-
border-color: t.color('
|
|
145
|
+
border-color: t.color('outline');
|
|
146
146
|
background-color: t.color('secondary-container');
|
|
147
147
|
color: t.color('on-secondary-container');
|
|
148
148
|
|
|
149
149
|
&:hover {
|
|
150
150
|
@include m.state-layer(t.color('on-secondary-container'), 'hover');
|
|
151
151
|
}
|
|
152
|
-
|
|
153
|
-
&:active {
|
|
154
|
-
@include m.state-layer(t.color('on-secondary-container'), 'pressed');
|
|
155
|
-
}
|
|
156
152
|
}
|
|
157
153
|
}
|
|
158
154
|
|
|
159
|
-
// Elevated chip
|
|
160
155
|
&--elevated {
|
|
161
156
|
background-color: t.color('surface-container-low');
|
|
162
157
|
color: t.color('on-surface');
|
|
@@ -179,109 +174,79 @@ $component: '#{base.$prefix}-chip';
|
|
|
179
174
|
&:hover {
|
|
180
175
|
@include m.state-layer(t.color('on-secondary-container'), 'hover');
|
|
181
176
|
}
|
|
182
|
-
|
|
183
|
-
&:active {
|
|
184
|
-
@include m.state-layer(t.color('on-secondary-container'), 'pressed');
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
&[aria-disabled="true"] {
|
|
189
|
-
@include m.elevation(0);
|
|
190
|
-
box-shadow: none;
|
|
191
177
|
}
|
|
192
178
|
}
|
|
193
179
|
|
|
194
|
-
//
|
|
195
|
-
&--
|
|
196
|
-
background-color: t.color('surface-container');
|
|
180
|
+
// Filter chip specific styling
|
|
181
|
+
&--filter {
|
|
182
|
+
background-color: t.color('surface-container-highest');
|
|
197
183
|
color: t.color('on-surface');
|
|
198
184
|
|
|
199
185
|
&:hover {
|
|
200
186
|
@include m.state-layer(t.color('on-surface'), 'hover');
|
|
201
187
|
}
|
|
202
188
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
189
|
+
&.#{$component}--selected {
|
|
190
|
+
background-color: t.color('secondary-container');
|
|
191
|
+
color: t.color('on-secondary-container');
|
|
192
|
+
|
|
193
|
+
.#{$component}-leading-icon {
|
|
194
|
+
color: t.color('on-secondary-container');
|
|
195
|
+
}
|
|
209
196
|
}
|
|
210
197
|
}
|
|
211
198
|
|
|
212
|
-
//
|
|
213
|
-
&--
|
|
214
|
-
background-color: t.color('surface-container-
|
|
199
|
+
// Assist chip specific styling
|
|
200
|
+
&--assist {
|
|
201
|
+
background-color: t.color('surface-container-low');
|
|
215
202
|
color: t.color('on-surface');
|
|
216
203
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
&:active {
|
|
222
|
-
@include m.state-layer(t.color('on-surface'), 'pressed');
|
|
204
|
+
.#{$component}-leading-icon {
|
|
205
|
+
color: t.color('primary');
|
|
223
206
|
}
|
|
224
207
|
|
|
225
208
|
&.#{$component}--selected {
|
|
226
209
|
background-color: t.color('secondary-container');
|
|
227
210
|
color: t.color('on-secondary-container');
|
|
228
211
|
|
|
229
|
-
|
|
230
|
-
@include m.state-layer(t.color('on-secondary-container'), 'hover');
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
&:active {
|
|
234
|
-
@include m.state-layer(t.color('on-secondary-container'), 'pressed');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Checkmark icon for selected filter chips
|
|
238
|
-
.#{$component}-icon {
|
|
212
|
+
.#{$component}-leading-icon {
|
|
239
213
|
color: t.color('on-secondary-container');
|
|
240
214
|
}
|
|
241
215
|
}
|
|
242
216
|
}
|
|
243
217
|
|
|
244
|
-
// Input chip
|
|
218
|
+
// Input chip specific styling
|
|
245
219
|
&--input {
|
|
246
220
|
background-color: t.color('surface-container-highest');
|
|
247
221
|
color: t.color('on-surface');
|
|
248
|
-
padding-right: 8px; // Less padding on the right to accommodate the trailing icon
|
|
249
|
-
|
|
250
|
-
&:hover {
|
|
251
|
-
@include m.state-layer(t.color('on-surface'), 'hover');
|
|
252
|
-
}
|
|
253
222
|
|
|
254
223
|
.#{$component}-trailing-icon {
|
|
255
|
-
|
|
224
|
+
cursor: pointer;
|
|
256
225
|
|
|
257
226
|
&:hover {
|
|
258
|
-
color: t.color('
|
|
227
|
+
color: t.color('error');
|
|
259
228
|
}
|
|
260
229
|
}
|
|
261
230
|
}
|
|
262
231
|
|
|
263
|
-
// Suggestion chip
|
|
232
|
+
// Suggestion chip styling
|
|
264
233
|
&--suggestion {
|
|
265
234
|
background-color: t.color('surface-container');
|
|
266
235
|
color: t.color('on-surface');
|
|
267
236
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
&:active {
|
|
273
|
-
@include m.state-layer(t.color('on-surface'), 'pressed');
|
|
237
|
+
&.#{$component}--selected {
|
|
238
|
+
background-color: t.color('secondary-container');
|
|
239
|
+
color: t.color('on-secondary-container');
|
|
274
240
|
}
|
|
275
241
|
}
|
|
276
242
|
|
|
277
|
-
//
|
|
278
|
-
|
|
243
|
+
// Size variants
|
|
279
244
|
&--small {
|
|
280
245
|
height: 24px;
|
|
281
246
|
padding: 0 8px;
|
|
282
247
|
font-size: 12px;
|
|
283
248
|
|
|
284
|
-
.#{$component}-icon,
|
|
249
|
+
.#{$component}-leading-icon,
|
|
285
250
|
.#{$component}-trailing-icon {
|
|
286
251
|
width: 16px;
|
|
287
252
|
height: 16px;
|
|
@@ -291,23 +256,18 @@ $component: '#{base.$prefix}-chip';
|
|
|
291
256
|
height: 16px;
|
|
292
257
|
}
|
|
293
258
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
.#{$component}-trailing-icon {
|
|
300
|
-
margin-left: 4px;
|
|
301
|
-
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
&--medium {
|
|
262
|
+
// Default size, styles already defined
|
|
302
263
|
}
|
|
303
264
|
|
|
304
265
|
&--large {
|
|
305
266
|
height: 40px;
|
|
306
267
|
padding: 0 16px;
|
|
307
|
-
font-size:
|
|
308
|
-
border-radius: 12px;
|
|
268
|
+
font-size: 16px;
|
|
309
269
|
|
|
310
|
-
.#{$component}-icon,
|
|
270
|
+
.#{$component}-leading-icon,
|
|
311
271
|
.#{$component}-trailing-icon {
|
|
312
272
|
width: 20px;
|
|
313
273
|
height: 20px;
|
|
@@ -317,40 +277,11 @@ $component: '#{base.$prefix}-chip';
|
|
|
317
277
|
height: 20px;
|
|
318
278
|
}
|
|
319
279
|
}
|
|
320
|
-
|
|
321
|
-
.#{$component}-icon {
|
|
322
|
-
margin-right: 10px;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
.#{$component}-trailing-icon {
|
|
326
|
-
margin-left: 10px;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// === SPECIAL CASES ===
|
|
331
|
-
|
|
332
|
-
// For chips with only icons (no text)
|
|
333
|
-
&--icon-only {
|
|
334
|
-
padding: 0;
|
|
335
|
-
width: 32px;
|
|
336
|
-
|
|
337
|
-
&.#{$component}--small {
|
|
338
|
-
width: 24px;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
&.#{$component}--large {
|
|
342
|
-
width: 40px;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
.#{$component}-icon {
|
|
346
|
-
margin-right: 0;
|
|
347
|
-
}
|
|
348
280
|
}
|
|
349
281
|
}
|
|
350
282
|
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
.#{$component}-set {
|
|
283
|
+
// Chip set container
|
|
284
|
+
.#{base.$prefix}-chip-set {
|
|
354
285
|
display: flex;
|
|
355
286
|
flex-wrap: wrap;
|
|
356
287
|
gap: 8px;
|
|
@@ -358,11 +289,23 @@ $component: '#{base.$prefix}-chip';
|
|
|
358
289
|
&--scrollable {
|
|
359
290
|
flex-wrap: nowrap;
|
|
360
291
|
overflow-x: auto;
|
|
361
|
-
|
|
292
|
+
padding-bottom: 8px;
|
|
293
|
+
margin-bottom: -8px; // Compensate for padding to maintain vertical alignment
|
|
294
|
+
-webkit-overflow-scrolling: touch; // Smooth scrolling on iOS
|
|
362
295
|
|
|
296
|
+
// Hide scrollbar in various browsers while maintaining functionality
|
|
363
297
|
&::-webkit-scrollbar {
|
|
364
|
-
|
|
298
|
+
height: 4px;
|
|
365
299
|
}
|
|
300
|
+
|
|
301
|
+
&::-webkit-scrollbar-thumb {
|
|
302
|
+
background-color: t.alpha('on-surface', 0.2);
|
|
303
|
+
border-radius: 4px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Style for Firefox
|
|
307
|
+
scrollbar-width: thin;
|
|
308
|
+
scrollbar-color: t.alpha('on-surface', 0.2) transparent;
|
|
366
309
|
}
|
|
367
310
|
|
|
368
311
|
&--vertical {
|
|
@@ -8,108 +8,237 @@
|
|
|
8
8
|
* @returns {Function} Higher-order function that adds API methods to component
|
|
9
9
|
* @internal This is an internal utility for the Chip component
|
|
10
10
|
*/
|
|
11
|
-
export const withAPI = ({ disabled, lifecycle }) => (component) =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
getValue: () => component.element.getAttribute('data-value'),
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Sets the chip's value
|
|
23
|
-
* @param {string} value - Value to set
|
|
24
|
-
* @returns {Object} The chip instance for chaining
|
|
25
|
-
*/
|
|
26
|
-
setValue (value) {
|
|
27
|
-
component.element.setAttribute('data-value', value)
|
|
28
|
-
return this
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Enables the chip
|
|
33
|
-
* @returns {Object} The chip instance for chaining
|
|
34
|
-
*/
|
|
35
|
-
enable () {
|
|
36
|
-
disabled.enable()
|
|
37
|
-
component.element.setAttribute('aria-disabled', 'false')
|
|
38
|
-
return this
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Disables the chip
|
|
43
|
-
* @returns {Object} The chip instance for chaining
|
|
44
|
-
*/
|
|
45
|
-
disable () {
|
|
46
|
-
disabled.disable()
|
|
47
|
-
component.element.setAttribute('aria-disabled', 'true')
|
|
48
|
-
return this
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Sets the chip's text content
|
|
53
|
-
* @param {string} content - Text content
|
|
54
|
-
* @returns {Object} The chip instance for chaining
|
|
55
|
-
*/
|
|
56
|
-
setText (content) {
|
|
57
|
-
component.text.setText(content)
|
|
58
|
-
return this
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Gets the chip's text content
|
|
63
|
-
* @returns {string} The chip's text content
|
|
64
|
-
*/
|
|
65
|
-
getText () {
|
|
66
|
-
return component.text.getText()
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Sets the chip's leading icon
|
|
71
|
-
* @param {string} icon - Icon HTML content
|
|
72
|
-
* @returns {Object} The chip instance for chaining
|
|
73
|
-
*/
|
|
74
|
-
setIcon (icon) {
|
|
75
|
-
component.icon.setIcon(icon)
|
|
76
|
-
return this
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Gets the chip's icon content
|
|
81
|
-
* @returns {string} The chip's icon HTML
|
|
82
|
-
*/
|
|
83
|
-
getIcon () {
|
|
84
|
-
return component.icon.getIcon()
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Sets the chip's trailing icon
|
|
89
|
-
* @param {string} icon - Icon HTML content
|
|
90
|
-
* @returns {Object} The chip instance for chaining
|
|
91
|
-
*/
|
|
92
|
-
setTrailingIcon (icon) {
|
|
93
|
-
const trailingIconSelector = `.${component.getClass('chip')}-trailing-icon`
|
|
94
|
-
let trailingIconElement = component.element.querySelector(trailingIconSelector)
|
|
95
|
-
|
|
96
|
-
if (!trailingIconElement && icon) {
|
|
97
|
-
trailingIconElement = document.createElement('span')
|
|
98
|
-
trailingIconElement.className = `${component.getClass('chip')}-trailing-icon`
|
|
99
|
-
component.element.appendChild(trailingIconElement)
|
|
100
|
-
}
|
|
11
|
+
export const withAPI = ({ disabled, lifecycle }) => (component) => {
|
|
12
|
+
// Track selected state internally
|
|
13
|
+
let isSelected = component.element.classList.contains(`${component.getClass('chip')}--selected`);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...component,
|
|
17
|
+
element: component.element,
|
|
101
18
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Gets the chip's value
|
|
21
|
+
* @returns {string} The chip's value attribute
|
|
22
|
+
*/
|
|
23
|
+
getValue() {
|
|
24
|
+
return component.element.getAttribute('data-value') || '';
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sets the chip's value
|
|
29
|
+
* @param {string} value - Value to set
|
|
30
|
+
* @returns {Object} The chip instance for chaining
|
|
31
|
+
*/
|
|
32
|
+
setValue(value) {
|
|
33
|
+
component.element.setAttribute('data-value', value || '');
|
|
34
|
+
return this;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Checks if the chip is disabled
|
|
39
|
+
* @returns {boolean} True if the chip is disabled
|
|
40
|
+
*/
|
|
41
|
+
isDisabled() {
|
|
42
|
+
return component.element.getAttribute('aria-disabled') === 'true';
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Enables the chip
|
|
47
|
+
* @returns {Object} The chip instance for chaining
|
|
48
|
+
*/
|
|
49
|
+
enable() {
|
|
50
|
+
disabled.enable();
|
|
51
|
+
component.element.classList.remove(`${component.getClass('chip')}--disabled`);
|
|
52
|
+
component.element.setAttribute('aria-disabled', 'false');
|
|
53
|
+
component.element.setAttribute('tabindex', '0');
|
|
54
|
+
return this;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Disables the chip
|
|
59
|
+
* @returns {Object} The chip instance for chaining
|
|
60
|
+
*/
|
|
61
|
+
disable() {
|
|
62
|
+
disabled.disable();
|
|
63
|
+
component.element.classList.add(`${component.getClass('chip')}--disabled`);
|
|
64
|
+
component.element.setAttribute('aria-disabled', 'true');
|
|
65
|
+
component.element.setAttribute('tabindex', '-1');
|
|
66
|
+
return this;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sets the chip's text content
|
|
71
|
+
* @param {string} content - Text content
|
|
72
|
+
* @returns {Object} The chip instance for chaining
|
|
73
|
+
*/
|
|
74
|
+
setText(content) {
|
|
75
|
+
const containerSelector = `.${component.getClass('chip')}-content`;
|
|
76
|
+
const contentContainer = component.element.querySelector(containerSelector) || component.element;
|
|
77
|
+
|
|
78
|
+
const textSelector = `.${component.getClass('chip')}-text`;
|
|
79
|
+
let textElement = component.element.querySelector(textSelector);
|
|
80
|
+
|
|
81
|
+
if (!textElement && content) {
|
|
82
|
+
textElement = document.createElement('span');
|
|
83
|
+
textElement.className = `${component.getClass('chip')}-text`;
|
|
84
|
+
|
|
85
|
+
// Find the right position to insert (after leading icon if present, or as first child)
|
|
86
|
+
const leadingIcon = component.element.querySelector(`.${component.getClass('chip')}-leading-icon`);
|
|
87
|
+
if (leadingIcon) {
|
|
88
|
+
contentContainer.insertBefore(textElement, leadingIcon.nextSibling);
|
|
89
|
+
} else {
|
|
90
|
+
contentContainer.insertBefore(textElement, contentContainer.firstChild);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (textElement) {
|
|
95
|
+
textElement.textContent = content || '';
|
|
96
|
+
|
|
97
|
+
// Remove the element if content is empty
|
|
98
|
+
if (!content && textElement.parentNode) {
|
|
99
|
+
textElement.parentNode.removeChild(textElement);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return this;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets the chip's text content
|
|
108
|
+
* @returns {string} The chip's text content
|
|
109
|
+
*/
|
|
110
|
+
getText() {
|
|
111
|
+
const textElement = component.element.querySelector(`.${component.getClass('chip')}-text`);
|
|
112
|
+
return textElement ? textElement.textContent : '';
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sets the chip's leading icon (alias for setLeadingIcon)
|
|
117
|
+
* @param {string} icon - Icon HTML content
|
|
118
|
+
* @returns {Object} The chip instance for chaining
|
|
119
|
+
*/
|
|
120
|
+
setIcon(icon) {
|
|
121
|
+
return this.setLeadingIcon(icon);
|
|
122
|
+
},
|
|
105
123
|
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Gets the chip's icon content
|
|
126
|
+
* @returns {string} The chip's icon HTML
|
|
127
|
+
*/
|
|
128
|
+
getIcon() {
|
|
129
|
+
const iconElement = component.element.querySelector(`.${component.getClass('chip')}-leading-icon`);
|
|
130
|
+
return iconElement ? iconElement.innerHTML : '';
|
|
131
|
+
},
|
|
108
132
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
})
|
|
133
|
+
/**
|
|
134
|
+
* Sets the chip's leading icon
|
|
135
|
+
* @param {string} icon - Icon HTML content
|
|
136
|
+
* @returns {Object} The chip instance for chaining
|
|
137
|
+
*/
|
|
138
|
+
setLeadingIcon(icon) {
|
|
139
|
+
const contentContainer = component.element.querySelector(`.${component.getClass('chip')}-content`) || component.element;
|
|
140
|
+
const leadingIconSelector = `.${component.getClass('chip')}-leading-icon`;
|
|
141
|
+
let leadingIconElement = component.element.querySelector(leadingIconSelector);
|
|
142
|
+
|
|
143
|
+
if (!leadingIconElement && icon) {
|
|
144
|
+
leadingIconElement = document.createElement('span');
|
|
145
|
+
leadingIconElement.className = `${component.getClass('chip')}-leading-icon`;
|
|
146
|
+
|
|
147
|
+
// Insert as first child of the content container
|
|
148
|
+
contentContainer.insertBefore(leadingIconElement, contentContainer.firstChild);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (leadingIconElement) {
|
|
152
|
+
leadingIconElement.innerHTML = icon || '';
|
|
153
|
+
|
|
154
|
+
// Remove the element if icon is empty
|
|
155
|
+
if (!icon && leadingIconElement.parentNode) {
|
|
156
|
+
leadingIconElement.parentNode.removeChild(leadingIconElement);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return this;
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Sets the chip's trailing icon
|
|
165
|
+
* @param {string} icon - Icon HTML content
|
|
166
|
+
* @param {Function} [onClick] - Click handler for the trailing icon
|
|
167
|
+
* @returns {Object} The chip instance for chaining
|
|
168
|
+
*/
|
|
169
|
+
setTrailingIcon(icon, onClick) {
|
|
170
|
+
const contentContainer = component.element.querySelector(`.${component.getClass('chip')}-content`) || component.element;
|
|
171
|
+
const trailingIconSelector = `.${component.getClass('chip')}-trailing-icon`;
|
|
172
|
+
let trailingIconElement = component.element.querySelector(trailingIconSelector);
|
|
173
|
+
|
|
174
|
+
if (!trailingIconElement && icon) {
|
|
175
|
+
trailingIconElement = document.createElement('span');
|
|
176
|
+
trailingIconElement.className = `${component.getClass('chip')}-trailing-icon`;
|
|
177
|
+
|
|
178
|
+
// Add as last child of content container
|
|
179
|
+
contentContainer.appendChild(trailingIconElement);
|
|
180
|
+
|
|
181
|
+
// Add click handler if provided
|
|
182
|
+
if (onClick) {
|
|
183
|
+
trailingIconElement.addEventListener('click', (e) => {
|
|
184
|
+
e.stopPropagation(); // Prevent chip click event
|
|
185
|
+
onClick(this);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (trailingIconElement) {
|
|
191
|
+
trailingIconElement.innerHTML = icon || '';
|
|
192
|
+
|
|
193
|
+
// Remove the element if icon is empty
|
|
194
|
+
if (!icon && trailingIconElement.parentNode) {
|
|
195
|
+
trailingIconElement.parentNode.removeChild(trailingIconElement);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return this;
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Checks if the chip is selected
|
|
204
|
+
* @returns {boolean} True if the chip is selected
|
|
205
|
+
*/
|
|
206
|
+
isSelected() {
|
|
207
|
+
return isSelected;
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Sets the chip's selected state
|
|
212
|
+
* @param {boolean} selected - Whether the chip should be selected
|
|
213
|
+
* @returns {Object} The chip instance for chaining
|
|
214
|
+
*/
|
|
215
|
+
setSelected(selected) {
|
|
216
|
+
isSelected = !!selected;
|
|
217
|
+
|
|
218
|
+
if (selected) {
|
|
219
|
+
component.element.classList.add(`${component.getClass('chip')}--selected`);
|
|
220
|
+
component.element.setAttribute('aria-selected', 'true');
|
|
221
|
+
} else {
|
|
222
|
+
component.element.classList.remove(`${component.getClass('chip')}--selected`);
|
|
223
|
+
component.element.setAttribute('aria-selected', 'false');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return this;
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Toggles the chip's selected state
|
|
231
|
+
* @returns {Object} The chip instance for chaining
|
|
232
|
+
*/
|
|
233
|
+
toggleSelected() {
|
|
234
|
+
return this.setSelected(!isSelected);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Destroys the chip component and cleans up resources
|
|
239
|
+
*/
|
|
240
|
+
destroy() {
|
|
241
|
+
lifecycle.destroy();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// src/components/chip/chip.
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
// src/components/chip/chip.js
|
|
2
|
+
import { PREFIX } from '../../core/config'
|
|
3
|
+
import { pipe } from '../../core/compose'
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component'
|
|
4
5
|
import {
|
|
5
6
|
withEvents,
|
|
6
7
|
withText,
|
|
@@ -10,72 +11,383 @@ import {
|
|
|
10
11
|
withRipple,
|
|
11
12
|
withDisabled,
|
|
12
13
|
withLifecycle
|
|
13
|
-
} from '../../core/compose/features'
|
|
14
|
-
import { withAPI } from './api'
|
|
15
|
-
import {
|
|
16
|
-
import { createBaseConfig, getElementConfig, getApiConfig } from './config';
|
|
14
|
+
} from '../../core/compose/features'
|
|
15
|
+
import { withAPI } from './api'
|
|
16
|
+
import { CHIP_VARIANTS, CHIP_SIZES } from './constants'
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Creates a new Chip component
|
|
20
|
-
* @param {
|
|
21
|
-
* @
|
|
20
|
+
* @param {Object} config - Chip configuration
|
|
21
|
+
* @param {string} [config.variant='filled'] - Chip variant
|
|
22
|
+
* @param {string} [config.size='medium'] - Chip size
|
|
23
|
+
* @param {boolean} [config.selected=false] - Whether the chip is initially selected
|
|
24
|
+
* @param {boolean} [config.disabled=false] - Whether the chip is initially disabled
|
|
25
|
+
* @param {string} [config.text] - Chip text content
|
|
26
|
+
* @param {string} [config.leadingIcon] - Leading icon HTML content
|
|
27
|
+
* @param {string} [config.trailingIcon] - Trailing icon HTML content
|
|
28
|
+
* @param {string} [config.class] - Additional CSS classes
|
|
29
|
+
* @param {string} [config.value] - Chip value
|
|
30
|
+
* @param {boolean} [config.ripple=true] - Whether to enable ripple effect
|
|
31
|
+
* @param {Function} [config.onTrailingIconClick] - Callback when trailing icon is clicked
|
|
32
|
+
* @param {Function} [config.onSelect] - Callback when chip is selected
|
|
33
|
+
* @param {Function} [config.onChange] - Callback when chip selection changes
|
|
34
|
+
* @returns {Object} Chip component instance
|
|
22
35
|
*/
|
|
23
|
-
const createChip = (config
|
|
24
|
-
const baseConfig =
|
|
36
|
+
const createChip = (config = {}) => {
|
|
37
|
+
const baseConfig = {
|
|
38
|
+
...config,
|
|
39
|
+
variant: config.variant || CHIP_VARIANTS.FILLED,
|
|
40
|
+
size: config.size || CHIP_SIZES.MEDIUM,
|
|
41
|
+
componentName: 'chip',
|
|
42
|
+
prefix: PREFIX,
|
|
43
|
+
ripple: config.ripple !== false
|
|
44
|
+
}
|
|
25
45
|
|
|
26
46
|
try {
|
|
47
|
+
// Create base component with core features
|
|
27
48
|
const chip = pipe(
|
|
28
49
|
createBase,
|
|
29
50
|
withEvents(),
|
|
30
|
-
withElement(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
withElement({
|
|
52
|
+
tag: 'div',
|
|
53
|
+
componentName: 'chip',
|
|
54
|
+
attrs: {
|
|
55
|
+
role: 'button',
|
|
56
|
+
tabindex: '0',
|
|
57
|
+
'aria-disabled': config.disabled ? 'true' : 'false',
|
|
58
|
+
'aria-selected': config.selected ? 'true' : 'false',
|
|
59
|
+
'data-value': config.value || ''
|
|
60
|
+
},
|
|
61
|
+
className: config.class,
|
|
62
|
+
forwardEvents: {
|
|
63
|
+
click: (component) => component.element.getAttribute('aria-disabled') !== 'true',
|
|
64
|
+
focus: true,
|
|
65
|
+
blur: true
|
|
66
|
+
}
|
|
38
67
|
}),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
68
|
+
withLifecycle()
|
|
69
|
+
)(baseConfig)
|
|
70
|
+
|
|
71
|
+
// Track selected state
|
|
72
|
+
let isSelectedState = !!config.selected;
|
|
73
|
+
|
|
74
|
+
// Manually add the variant class
|
|
75
|
+
if (config.variant) {
|
|
76
|
+
chip.element.classList.add(`${chip.getClass('chip')}--${config.variant}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Manually add the size class
|
|
80
|
+
if (config.size) {
|
|
81
|
+
chip.element.classList.add(`${chip.getClass('chip')}--${config.size}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Add ripple if enabled
|
|
85
|
+
if (config.ripple) {
|
|
86
|
+
withRipple(baseConfig)(chip)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Add disabled state if needed
|
|
90
|
+
if (config.disabled) {
|
|
91
|
+
withDisabled(baseConfig)(chip)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add selected class if needed
|
|
95
|
+
if (config.selected) {
|
|
96
|
+
chip.element.classList.add(`${chip.getClass('chip')}--selected`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create a container for the chip content to ensure proper ordering
|
|
100
|
+
const contentContainer = document.createElement('div')
|
|
101
|
+
contentContainer.className = `${chip.getClass('chip')}-content`
|
|
102
|
+
contentContainer.style.display = 'flex'
|
|
103
|
+
contentContainer.style.alignItems = 'center'
|
|
104
|
+
contentContainer.style.justifyContent = 'center'
|
|
105
|
+
contentContainer.style.width = '100%'
|
|
106
|
+
chip.element.appendChild(contentContainer)
|
|
107
|
+
|
|
108
|
+
// Add leading icon if provided
|
|
109
|
+
if (config.leadingIcon) {
|
|
110
|
+
const leadingIconElement = document.createElement('span')
|
|
111
|
+
leadingIconElement.className = `${chip.getClass('chip')}-leading-icon`
|
|
112
|
+
leadingIconElement.innerHTML = config.leadingIcon
|
|
113
|
+
contentContainer.appendChild(leadingIconElement)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add text element if provided
|
|
117
|
+
if (config.text) {
|
|
118
|
+
const textElement = document.createElement('span')
|
|
119
|
+
textElement.className = `${chip.getClass('chip')}-text`
|
|
120
|
+
textElement.textContent = config.text
|
|
121
|
+
contentContainer.appendChild(textElement)
|
|
122
|
+
}
|
|
44
123
|
|
|
45
124
|
// Add trailing icon if provided
|
|
46
125
|
if (config.trailingIcon) {
|
|
47
|
-
const trailingIconElement = document.createElement('span')
|
|
48
|
-
trailingIconElement.className = `${
|
|
49
|
-
trailingIconElement.innerHTML = config.trailingIcon
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// Add event listener for remove/close action if needed
|
|
126
|
+
const trailingIconElement = document.createElement('span')
|
|
127
|
+
trailingIconElement.className = `${chip.getClass('chip')}-trailing-icon`
|
|
128
|
+
trailingIconElement.innerHTML = config.trailingIcon
|
|
129
|
+
|
|
130
|
+
// Add click handler for trailing icon
|
|
53
131
|
if (config.onTrailingIconClick) {
|
|
54
132
|
trailingIconElement.addEventListener('click', (e) => {
|
|
55
|
-
e.stopPropagation()
|
|
56
|
-
config.onTrailingIconClick
|
|
57
|
-
})
|
|
133
|
+
e.stopPropagation() // Prevent chip click event
|
|
134
|
+
config.onTrailingIconClick(enhancedChip)
|
|
135
|
+
})
|
|
58
136
|
}
|
|
137
|
+
|
|
138
|
+
contentContainer.appendChild(trailingIconElement)
|
|
59
139
|
}
|
|
60
140
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
141
|
+
// Create enhanced component with API
|
|
142
|
+
const enhancedChip = {
|
|
143
|
+
...chip,
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Checks if the chip is disabled
|
|
147
|
+
* @returns {boolean} True if the chip is disabled
|
|
148
|
+
*/
|
|
149
|
+
isDisabled() {
|
|
150
|
+
return chip.element.getAttribute('aria-disabled') === 'true';
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Checks if the chip is selected
|
|
155
|
+
* @returns {boolean} True if the chip is selected
|
|
156
|
+
*/
|
|
157
|
+
isSelected() {
|
|
158
|
+
return isSelectedState;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sets the chip's selected state
|
|
163
|
+
* @param {boolean} selected - Whether the chip should be selected
|
|
164
|
+
* @returns {Object} The chip instance for chaining
|
|
165
|
+
*/
|
|
166
|
+
setSelected(selected) {
|
|
167
|
+
isSelectedState = !!selected;
|
|
168
|
+
|
|
169
|
+
if (selected) {
|
|
170
|
+
chip.element.classList.add(`${chip.getClass('chip')}--selected`);
|
|
171
|
+
chip.element.setAttribute('aria-selected', 'true');
|
|
172
|
+
} else {
|
|
173
|
+
chip.element.classList.remove(`${chip.getClass('chip')}--selected`);
|
|
174
|
+
chip.element.setAttribute('aria-selected', 'false');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return this;
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Toggles the chip's selected state
|
|
182
|
+
* @returns {Object} The chip instance for chaining
|
|
183
|
+
*/
|
|
184
|
+
toggleSelected() {
|
|
185
|
+
return this.setSelected(!isSelectedState);
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Gets the chip's value
|
|
190
|
+
* @returns {string} The chip's value
|
|
191
|
+
*/
|
|
192
|
+
getValue() {
|
|
193
|
+
return chip.element.getAttribute('data-value');
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Sets the chip's value
|
|
198
|
+
* @param {string} value - Value to set
|
|
199
|
+
* @returns {Object} The chip instance for chaining
|
|
200
|
+
*/
|
|
201
|
+
setValue(value) {
|
|
202
|
+
chip.element.setAttribute('data-value', value);
|
|
203
|
+
return this;
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Enables the chip
|
|
208
|
+
* @returns {Object} The chip instance for chaining
|
|
209
|
+
*/
|
|
210
|
+
enable() {
|
|
211
|
+
chip.element.classList.remove(`${chip.getClass('chip')}--disabled`);
|
|
212
|
+
chip.element.setAttribute('aria-disabled', 'false');
|
|
213
|
+
chip.element.setAttribute('tabindex', '0');
|
|
214
|
+
return this;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Disables the chip
|
|
219
|
+
* @returns {Object} The chip instance for chaining
|
|
220
|
+
*/
|
|
221
|
+
disable() {
|
|
222
|
+
chip.element.classList.add(`${chip.getClass('chip')}--disabled`);
|
|
223
|
+
chip.element.setAttribute('aria-disabled', 'true');
|
|
224
|
+
chip.element.setAttribute('tabindex', '-1');
|
|
225
|
+
return this;
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Sets the chip's text content
|
|
230
|
+
* @param {string} content - Text content
|
|
231
|
+
* @returns {Object} The chip instance for chaining
|
|
232
|
+
*/
|
|
233
|
+
setText(content) {
|
|
234
|
+
const textElement = chip.element.querySelector(`.${chip.getClass('chip')}-text`);
|
|
235
|
+
|
|
236
|
+
if (textElement) {
|
|
237
|
+
textElement.textContent = content;
|
|
238
|
+
} else if (content) {
|
|
239
|
+
const newTextElement = document.createElement('span');
|
|
240
|
+
newTextElement.className = `${chip.getClass('chip')}-text`;
|
|
241
|
+
newTextElement.textContent = content;
|
|
242
|
+
contentContainer.appendChild(newTextElement);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return this;
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Gets the chip's text content
|
|
250
|
+
* @returns {string} The chip's text content
|
|
251
|
+
*/
|
|
252
|
+
getText() {
|
|
253
|
+
const textElement = chip.element.querySelector(`.${chip.getClass('chip')}-text`);
|
|
254
|
+
return textElement ? textElement.textContent : '';
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Sets the chip's icon
|
|
259
|
+
* @param {string} icon - Icon HTML content
|
|
260
|
+
* @returns {Object} The chip instance for chaining
|
|
261
|
+
*/
|
|
262
|
+
setIcon(icon) {
|
|
263
|
+
return this.setLeadingIcon(icon);
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Gets the chip's icon
|
|
268
|
+
* @returns {string} The chip's icon HTML
|
|
269
|
+
*/
|
|
270
|
+
getIcon() {
|
|
271
|
+
const iconElement = chip.element.querySelector(`.${chip.getClass('chip')}-leading-icon`);
|
|
272
|
+
return iconElement ? iconElement.innerHTML : '';
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Sets the chip's leading icon
|
|
277
|
+
* @param {string} icon - Icon HTML content
|
|
278
|
+
* @returns {Object} The chip instance for chaining
|
|
279
|
+
*/
|
|
280
|
+
setLeadingIcon(icon) {
|
|
281
|
+
const leadingIconSelector = `.${chip.getClass('chip')}-leading-icon`;
|
|
282
|
+
let leadingIconElement = chip.element.querySelector(leadingIconSelector);
|
|
283
|
+
|
|
284
|
+
if (!leadingIconElement && icon) {
|
|
285
|
+
leadingIconElement = document.createElement('span');
|
|
286
|
+
leadingIconElement.className = `${chip.getClass('chip')}-leading-icon`;
|
|
287
|
+
|
|
288
|
+
// Insert at the beginning of the content container
|
|
289
|
+
contentContainer.insertBefore(leadingIconElement, contentContainer.firstChild);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (leadingIconElement) {
|
|
293
|
+
leadingIconElement.innerHTML = icon || '';
|
|
294
|
+
|
|
295
|
+
// Remove the element if icon is empty
|
|
296
|
+
if (!icon && leadingIconElement.parentNode) {
|
|
297
|
+
leadingIconElement.parentNode.removeChild(leadingIconElement);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return this;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Sets the chip's trailing icon
|
|
306
|
+
* @param {string} icon - Icon HTML content
|
|
307
|
+
* @param {Function} [onClick] - Click handler for the trailing icon
|
|
308
|
+
* @returns {Object} The chip instance for chaining
|
|
309
|
+
*/
|
|
310
|
+
setTrailingIcon(icon, onClick) {
|
|
311
|
+
const trailingIconSelector = `.${chip.getClass('chip')}-trailing-icon`;
|
|
312
|
+
let trailingIconElement = chip.element.querySelector(trailingIconSelector);
|
|
313
|
+
|
|
314
|
+
if (!trailingIconElement && icon) {
|
|
315
|
+
trailingIconElement = document.createElement('span');
|
|
316
|
+
trailingIconElement.className = `${chip.getClass('chip')}-trailing-icon`;
|
|
317
|
+
|
|
318
|
+
// Add at the end of the content container
|
|
319
|
+
contentContainer.appendChild(trailingIconElement);
|
|
320
|
+
|
|
321
|
+
// Add click handler if provided
|
|
322
|
+
if (onClick) {
|
|
323
|
+
trailingIconElement.addEventListener('click', (e) => {
|
|
324
|
+
e.stopPropagation(); // Prevent chip click event
|
|
325
|
+
onClick(this);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (trailingIconElement) {
|
|
331
|
+
trailingIconElement.innerHTML = icon || '';
|
|
332
|
+
|
|
333
|
+
// Remove the element if icon is empty
|
|
334
|
+
if (!icon && trailingIconElement.parentNode) {
|
|
335
|
+
trailingIconElement.parentNode.removeChild(trailingIconElement);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return this;
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Destroys the chip component and cleans up resources
|
|
344
|
+
*/
|
|
345
|
+
destroy() {
|
|
346
|
+
chip.lifecycle && chip.lifecycle.destroy && chip.lifecycle.destroy();
|
|
347
|
+
chip.element.remove();
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
// Forward event methods from the original chip
|
|
351
|
+
on: chip.on,
|
|
352
|
+
off: chip.off,
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Add CSS classes to the chip element
|
|
356
|
+
* @param {...string} classes - CSS classes to add
|
|
357
|
+
* @returns {Object} The chip instance for chaining
|
|
358
|
+
*/
|
|
359
|
+
addClass(...classes) {
|
|
360
|
+
chip.element.classList.add(...classes);
|
|
361
|
+
return this;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
65
364
|
|
|
66
|
-
//
|
|
67
|
-
if (config.
|
|
365
|
+
// Add click handler for selection toggle
|
|
366
|
+
if (config.variant === CHIP_VARIANTS.FILTER ||
|
|
367
|
+
config.variant === CHIP_VARIANTS.ASSIST ||
|
|
368
|
+
config.selectable) {
|
|
369
|
+
|
|
68
370
|
chip.element.addEventListener('click', () => {
|
|
69
|
-
if (
|
|
70
|
-
|
|
371
|
+
if (enhancedChip.isDisabled()) return;
|
|
372
|
+
|
|
373
|
+
enhancedChip.toggleSelected();
|
|
374
|
+
|
|
375
|
+
// Call onChange callback if provided
|
|
376
|
+
if (config.onChange) {
|
|
377
|
+
config.onChange(enhancedChip);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Call onSelect callback if provided
|
|
381
|
+
if (config.onSelect) {
|
|
382
|
+
config.onSelect(enhancedChip);
|
|
71
383
|
}
|
|
72
384
|
});
|
|
73
385
|
}
|
|
74
386
|
|
|
75
|
-
return
|
|
387
|
+
return enhancedChip;
|
|
76
388
|
} catch (error) {
|
|
77
|
-
console.error('Chip creation error:', error
|
|
78
|
-
throw new Error(`Failed to create chip: ${error
|
|
389
|
+
console.error('Chip creation error:', error);
|
|
390
|
+
throw new Error(`Failed to create chip: ${error.message}`);
|
|
79
391
|
}
|
|
80
392
|
};
|
|
81
393
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/components/chip/constants.
|
|
1
|
+
// src/components/chip/constants.js
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Available variants for the Chip component
|
|
@@ -25,7 +25,7 @@ export const CHIP_VARIANTS = {
|
|
|
25
25
|
|
|
26
26
|
/** Suggestion chip for presenting options */
|
|
27
27
|
SUGGESTION: 'suggestion'
|
|
28
|
-
}
|
|
28
|
+
};
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Available sizes for the Chip component
|
|
@@ -35,4 +35,4 @@ export const CHIP_SIZES = {
|
|
|
35
35
|
SMALL: 'small',
|
|
36
36
|
MEDIUM: 'medium',
|
|
37
37
|
LARGE: 'large'
|
|
38
|
-
}
|
|
38
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/components/chip/index.
|
|
1
|
+
// src/components/chip/index.js
|
|
2
2
|
export { default } from './chip'
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
3
|
+
export { default as createChipSet } from './chip-set'
|
|
4
|
+
export { CHIP_VARIANTS, CHIP_SIZES } from './constants'
|
|
@@ -180,6 +180,18 @@ $z-index: (
|
|
|
180
180
|
) !default;
|
|
181
181
|
|
|
182
182
|
// Component-specific tokens
|
|
183
|
+
@function card($key) {
|
|
184
|
+
$card: (
|
|
185
|
+
'width': 344px,
|
|
186
|
+
'width-small': 344px,
|
|
187
|
+
'width-medium': 480px,
|
|
188
|
+
'width-large': 624px,
|
|
189
|
+
'border-radius': 12px,
|
|
190
|
+
'padding': 16px
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
@return map-get($card, $key);
|
|
194
|
+
}
|
|
183
195
|
$button: (
|
|
184
196
|
'height': 40px,
|
|
185
197
|
'min-width': 64px,
|