@oslokommune/punkt-elements 13.4.1 → 13.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-elements",
3
- "version": "13.4.1",
3
+ "version": "13.4.2",
4
4
  "description": "Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo",
5
5
  "homepage": "https://punkt.oslo.kommune.no",
6
6
  "author": "Team Designsystem, Oslo Origo",
@@ -69,5 +69,5 @@
69
69
  "url": "https://github.com/oslokommune/punkt/issues"
70
70
  },
71
71
  "license": "MIT",
72
- "gitHead": "5768a452bf48f03c7448b9706ad1cb2c06d3fdc4"
72
+ "gitHead": "b8dc1a2d10d756fb0fc6babb0f3b03b4d8eea95a"
73
73
  }
@@ -0,0 +1,592 @@
1
+ import '@testing-library/jest-dom'
2
+ import { axe, toHaveNoViolations } from 'jest-axe'
3
+
4
+ expect.extend(toHaveNoViolations)
5
+
6
+ import './card'
7
+ import { PktCard } from './card'
8
+
9
+ const waitForCustomElements = async () => {
10
+ await customElements.whenDefined('pkt-card')
11
+ }
12
+
13
+ // Helper function to create card markup
14
+ const createCard = async (cardProps = '', content = 'Card content') => {
15
+ const container = document.createElement('div')
16
+ container.innerHTML = `
17
+ <pkt-card ${cardProps}>
18
+ ${content}
19
+ </pkt-card>
20
+ `
21
+ document.body.appendChild(container)
22
+ await waitForCustomElements()
23
+ return container
24
+ }
25
+
26
+ // Cleanup after each test
27
+ afterEach(() => {
28
+ document.body.innerHTML = ''
29
+ })
30
+
31
+ describe('PktCard', () => {
32
+ describe('Rendering and basic functionality', () => {
33
+ test('renders without errors', async () => {
34
+ const container = await createCard()
35
+
36
+ const card = container.querySelector('pkt-card') as PktCard
37
+ expect(card).toBeInTheDocument()
38
+
39
+ await card.updateComplete
40
+ expect(card).toBeTruthy()
41
+
42
+ const article = card.querySelector('article')
43
+ expect(article).toBeInTheDocument()
44
+ expect(article).toHaveClass('pkt-card')
45
+ })
46
+
47
+ test('renders content in slot', async () => {
48
+ const container = await createCard('', '<p>Test content here</p>')
49
+
50
+ const card = container.querySelector('pkt-card') as PktCard
51
+ await card.updateComplete
52
+
53
+ const content = card.querySelector('.pkt-card__content')
54
+ expect(content).toBeInTheDocument()
55
+ expect(content?.textContent).toContain('Test content here')
56
+ })
57
+
58
+ test('renders basic structure correctly', async () => {
59
+ const container = await createCard('heading="Test Heading"', 'Test content')
60
+
61
+ const card = container.querySelector('pkt-card') as PktCard
62
+ await card.updateComplete
63
+
64
+ const article = card.querySelector('article')
65
+ const wrapper = article?.querySelector('.pkt-card__wrapper')
66
+ const header = wrapper?.querySelector('.pkt-card__header')
67
+ const content = wrapper?.querySelector('.pkt-card__content')
68
+
69
+ expect(wrapper).toBeInTheDocument()
70
+ expect(header).toBeInTheDocument()
71
+ expect(content).toBeInTheDocument()
72
+ })
73
+ })
74
+
75
+ describe('Properties and attributes', () => {
76
+ test('applies default properties correctly', async () => {
77
+ const container = await createCard()
78
+
79
+ const card = container.querySelector('pkt-card') as PktCard
80
+ await card.updateComplete
81
+
82
+ expect(card.skin).toBe('outlined')
83
+ expect(card.layout).toBe('vertical')
84
+ expect(card.padding).toBe('default')
85
+ expect(card.borderOnHover).toBe(true)
86
+ expect(card.tagPosition).toBe('top')
87
+ expect(card.imageShape).toBe('square')
88
+ expect(card.openLinkInNewTab).toBe(false)
89
+ expect(card.headinglevel).toBe(3)
90
+
91
+ const article = card.querySelector('article')
92
+ expect(article).toHaveClass('pkt-card--outlined')
93
+ expect(article).toHaveClass('pkt-card--vertical')
94
+ expect(article).toHaveClass('pkt-card--padding-default')
95
+ expect(article).toHaveClass('pkt-card--border-on-hover')
96
+ })
97
+
98
+ test('applies different skin properties correctly', async () => {
99
+ const skins = ['outlined', 'outlined-beige', 'gray', 'beige', 'green', 'blue']
100
+
101
+ for (const skin of skins) {
102
+ const container = await createCard(`skin="${skin}"`)
103
+ const card = container.querySelector('pkt-card') as PktCard
104
+ await card.updateComplete
105
+
106
+ expect(card.skin).toBe(skin)
107
+ expect(card.getAttribute('skin')).toBe(skin)
108
+
109
+ const article = card.querySelector('article')
110
+ expect(article).toHaveClass(`pkt-card--${skin}`)
111
+
112
+ // Cleanup for next iteration
113
+ container.remove()
114
+ }
115
+ })
116
+
117
+ test('rejects unsupported skin values and falls back to default', async () => {
118
+ const unsupportedSkins = ['zebra', 'goldenrod', 'hotpink', 'rainbow', 'invalid']
119
+
120
+ for (const invalidSkin of unsupportedSkins) {
121
+ const container = await createCard(`skin="${invalidSkin}"`)
122
+ const card = container.querySelector('pkt-card') as PktCard
123
+ await card.updateComplete
124
+
125
+ // The component should now validate skin values and fall back to default
126
+ expect(card.skin).not.toBe(invalidSkin)
127
+ expect(card.skin).toBe('outlined') // Should fall back to default
128
+
129
+ const article = card.querySelector('article')
130
+ // Should not have the invalid CSS class
131
+ expect(article).not.toHaveClass(`pkt-card--${invalidSkin}`)
132
+ // Should have the default skin class instead
133
+ expect(article).toHaveClass('pkt-card--outlined')
134
+
135
+ // Cleanup for next iteration
136
+ container.remove()
137
+ }
138
+ })
139
+
140
+ test('validates skin values and logs warnings for invalid skins', async () => {
141
+ // Spy on console.warn to check if warning is logged
142
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
143
+
144
+ const container = await createCard(`skin="zebra"`)
145
+ const card = container.querySelector('pkt-card') as PktCard
146
+ await card.updateComplete
147
+
148
+ // Should have logged a warning with the correct default value from spec
149
+ expect(consoleSpy).toHaveBeenCalledWith(
150
+ 'Invalid skin value "zebra". Using default skin "outlined".',
151
+ )
152
+
153
+ // Should fall back to default from spec
154
+ expect(card.skin).toBe('outlined')
155
+
156
+ // Restore console.warn
157
+ consoleSpy.mockRestore()
158
+ })
159
+
160
+ test('applies different layout properties correctly', async () => {
161
+ const layouts = ['vertical', 'horizontal']
162
+
163
+ for (const layout of layouts) {
164
+ const container = await createCard(`layout="${layout}"`)
165
+ const card = container.querySelector('pkt-card') as PktCard
166
+ await card.updateComplete
167
+
168
+ expect(card.layout).toBe(layout)
169
+ expect(card.getAttribute('layout')).toBe(layout)
170
+
171
+ const article = card.querySelector('article')
172
+ expect(article).toHaveClass(`pkt-card--${layout}`)
173
+
174
+ // Cleanup for next iteration
175
+ container.remove()
176
+ }
177
+ })
178
+
179
+ test('applies different padding properties correctly', async () => {
180
+ const paddingOptions = ['none', 'default']
181
+
182
+ for (const padding of paddingOptions) {
183
+ const container = await createCard(`padding="${padding}"`)
184
+ const card = container.querySelector('pkt-card') as PktCard
185
+ await card.updateComplete
186
+
187
+ expect(card.padding).toBe(padding)
188
+ expect(card.getAttribute('padding')).toBe(padding)
189
+
190
+ const article = card.querySelector('article')
191
+ expect(article).toHaveClass(`pkt-card--padding-${padding}`)
192
+
193
+ // Cleanup for next iteration
194
+ container.remove()
195
+ }
196
+ })
197
+
198
+ test('handles borderOnHover property correctly', async () => {
199
+ // Test with borderOnHover false
200
+ const container = await createCard()
201
+ const card = container.querySelector('pkt-card') as PktCard
202
+ card.borderOnHover = false
203
+ await card.updateComplete
204
+
205
+ expect(card.borderOnHover).toBe(false)
206
+
207
+ const article = card.querySelector('article')
208
+ expect(article).not.toHaveClass('pkt-card--border-on-hover')
209
+ })
210
+ })
211
+
212
+ describe('Heading functionality', () => {
213
+ test('renders heading when provided', async () => {
214
+ const container = await createCard('heading="Test Card Title"')
215
+
216
+ const card = container.querySelector('pkt-card') as PktCard
217
+ await card.updateComplete
218
+
219
+ expect(card.heading).toBe('Test Card Title')
220
+
221
+ const heading = card.querySelector('pkt-heading')
222
+ expect(heading).toBeInTheDocument()
223
+ expect(heading).toHaveClass('pkt-card__heading')
224
+ expect(heading?.textContent?.trim()).toBe('Test Card Title')
225
+ })
226
+
227
+ test('renders subheading when provided', async () => {
228
+ const container = await createCard('subheading="Test Subheading"')
229
+
230
+ const card = container.querySelector('pkt-card') as PktCard
231
+ await card.updateComplete
232
+
233
+ expect(card.subheading).toBe('Test Subheading')
234
+
235
+ const subheading = card.querySelector('.pkt-card__subheading')
236
+ expect(subheading).toBeInTheDocument()
237
+ expect(subheading?.textContent).toBe('Test Subheading')
238
+ })
239
+
240
+ test('applies correct heading level', async () => {
241
+ const container = await createCard('heading="Test" headinglevel="2"')
242
+
243
+ const card = container.querySelector('pkt-card') as PktCard
244
+ await card.updateComplete
245
+
246
+ expect(card.headinglevel).toBe(2)
247
+
248
+ const heading = card.querySelector('pkt-heading')
249
+ expect(heading?.getAttribute('level')).toBe('2')
250
+ })
251
+
252
+ test('does not render header when no heading or subheading', async () => {
253
+ const container = await createCard()
254
+
255
+ const card = container.querySelector('pkt-card') as PktCard
256
+ await card.updateComplete
257
+
258
+ const header = card.querySelector('.pkt-card__header')
259
+ expect(header).not.toBeInTheDocument()
260
+ })
261
+ })
262
+
263
+ describe('Link functionality', () => {
264
+ test('renders as regular card when no clickCardLink', async () => {
265
+ const container = await createCard('heading="Test Title"')
266
+
267
+ const card = container.querySelector('pkt-card') as PktCard
268
+ await card.updateComplete
269
+
270
+ expect(card.clickCardLink).toBe(null)
271
+
272
+ const heading = card.querySelector('pkt-heading')
273
+ const link = card.querySelector('.pkt-card__link')
274
+ expect(heading).toBeInTheDocument()
275
+ expect(link).not.toBeInTheDocument()
276
+
277
+ const article = card.querySelector('article')
278
+ expect(article?.getAttribute('aria-label')).toBe('Test Title')
279
+ })
280
+
281
+ test('renders as link card when clickCardLink provided', async () => {
282
+ const container = await createCard('heading="Test Title"')
283
+ const card = container.querySelector('pkt-card') as PktCard
284
+ card.clickCardLink = '/test-url'
285
+ await card.updateComplete
286
+
287
+ expect(card.clickCardLink).toBe('/test-url')
288
+
289
+ const linkHeading = card.querySelector('.pkt-card__link-heading')
290
+ const link = card.querySelector('.pkt-card__link')
291
+ expect(linkHeading).toBeInTheDocument()
292
+ expect(link).toBeInTheDocument()
293
+ expect(link?.getAttribute('href')).toBe('/test-url')
294
+ expect(link?.textContent).toBe('Test Title')
295
+
296
+ const article = card.querySelector('article')
297
+ expect(article?.getAttribute('aria-label')).toBe('Test Title lenkekort')
298
+ })
299
+
300
+ test('handles openLinkInNewTab correctly', async () => {
301
+ const container = await createCard('heading="Test"')
302
+ const card = container.querySelector('pkt-card') as PktCard
303
+ card.clickCardLink = '/test'
304
+ card.openLinkInNewTab = true
305
+ await card.updateComplete
306
+
307
+ expect(card.openLinkInNewTab).toBe(true)
308
+
309
+ const link = card.querySelector('.pkt-card__link')
310
+ expect(link?.getAttribute('target')).toBe('_blank')
311
+ })
312
+
313
+ test('applies correct aria-label for link cards', async () => {
314
+ const container = await createCard()
315
+ const card = container.querySelector('pkt-card') as PktCard
316
+ card.clickCardLink = '/test'
317
+ card.ariaLabel = 'Custom aria label'
318
+ await card.updateComplete
319
+
320
+ const article = card.querySelector('article')
321
+ expect(article?.getAttribute('aria-label')).toBe('Custom aria label')
322
+ })
323
+ })
324
+
325
+ describe('Image functionality', () => {
326
+ test('renders image when provided', async () => {
327
+ const container = await createCard()
328
+ const card = container.querySelector('pkt-card') as PktCard
329
+ card.image = { src: '/test-image.jpg', alt: 'Test image' }
330
+ await card.updateComplete
331
+
332
+ expect(card.image.src).toBe('/test-image.jpg')
333
+ expect(card.image.alt).toBe('Test image')
334
+
335
+ const imageDiv = card.querySelector('.pkt-card__image')
336
+ const img = imageDiv?.querySelector('img')
337
+ expect(imageDiv).toBeInTheDocument()
338
+ expect(img).toBeInTheDocument()
339
+ expect(img?.getAttribute('src')).toBe('/test-image.jpg')
340
+ expect(img?.getAttribute('alt')).toBe('Test image')
341
+ })
342
+
343
+ test('does not render image when not provided', async () => {
344
+ const container = await createCard()
345
+
346
+ const card = container.querySelector('pkt-card') as PktCard
347
+ await card.updateComplete
348
+
349
+ const imageDiv = card.querySelector('.pkt-card__image')
350
+ expect(imageDiv).not.toBeInTheDocument()
351
+ })
352
+
353
+ test('applies correct image shape classes', async () => {
354
+ const shapes = ['square', 'round'] as const
355
+
356
+ for (const shape of shapes) {
357
+ const container = await createCard()
358
+ const card = container.querySelector('pkt-card') as PktCard
359
+ card.image = { src: '/test.jpg', alt: 'Test' }
360
+ card.imageShape = shape
361
+ await card.updateComplete
362
+
363
+ expect(card.imageShape).toBe(shape)
364
+
365
+ const imageDiv = card.querySelector('.pkt-card__image')
366
+ expect(imageDiv).toHaveClass(`pkt-card__image-${shape}`)
367
+
368
+ // Cleanup for next iteration
369
+ container.remove()
370
+ }
371
+ })
372
+ })
373
+
374
+ describe('Tags functionality', () => {
375
+ test('renders tags when provided', async () => {
376
+ const container = await createCard()
377
+ const card = container.querySelector('pkt-card') as PktCard
378
+ card.tags = [
379
+ { text: 'Tag 1', skin: 'blue' },
380
+ { text: 'Tag 2', skin: 'green' },
381
+ ]
382
+ await card.updateComplete
383
+
384
+ expect(card.tags).toHaveLength(2)
385
+
386
+ const tagsContainer = card.querySelector('.pkt-card__tags')
387
+ const tags = tagsContainer?.querySelectorAll('pkt-tag')
388
+ expect(tagsContainer).toBeInTheDocument()
389
+ expect(tags).toHaveLength(2)
390
+ expect(tagsContainer?.getAttribute('aria-label')).toBe('merkelapper')
391
+ })
392
+
393
+ test('renders single tag with correct aria-label', async () => {
394
+ const container = await createCard()
395
+ const card = container.querySelector('pkt-card') as PktCard
396
+ card.tags = [{ text: 'Single Tag' }]
397
+ await card.updateComplete
398
+
399
+ const tagsContainer = card.querySelector('.pkt-card__tags')
400
+ expect(tagsContainer?.getAttribute('aria-label')).toBe('merkelapp')
401
+ })
402
+
403
+ test('applies correct tag position classes', async () => {
404
+ const positions = ['top', 'bottom'] as const
405
+
406
+ for (const position of positions) {
407
+ const container = await createCard()
408
+ const card = container.querySelector('pkt-card') as PktCard
409
+ card.tags = [{ text: 'Test Tag' }]
410
+ card.tagPosition = position
411
+ await card.updateComplete
412
+
413
+ expect(card.tagPosition).toBe(position)
414
+
415
+ const tagsContainer = card.querySelector('.pkt-card__tags')
416
+ expect(tagsContainer).toHaveClass(`pkt-card__tags-${position}`)
417
+
418
+ // Cleanup for next iteration
419
+ container.remove()
420
+ }
421
+ })
422
+
423
+ test('does not render tags when array is empty', async () => {
424
+ const container = await createCard()
425
+
426
+ const card = container.querySelector('pkt-card') as PktCard
427
+ await card.updateComplete
428
+
429
+ const tagsContainer = card.querySelector('.pkt-card__tags')
430
+ expect(tagsContainer).not.toBeInTheDocument()
431
+ })
432
+ })
433
+
434
+ describe('Metadata functionality', () => {
435
+ test('renders metadata when provided', async () => {
436
+ const container = await createCard()
437
+ const card = container.querySelector('pkt-card') as PktCard
438
+ card.metaLead = 'Author Name'
439
+ card.metaTrail = '2023-12-01'
440
+ await card.updateComplete
441
+
442
+ expect(card.metaLead).toBe('Author Name')
443
+ expect(card.metaTrail).toBe('2023-12-01')
444
+
445
+ const metadata = card.querySelector('.pkt-card__metadata')
446
+ const metaLead = metadata?.querySelector('.pkt-card__metadata-lead')
447
+ const metaTrail = metadata?.querySelector('.pkt-card__metadata-trail')
448
+
449
+ expect(metadata).toBeInTheDocument()
450
+ expect(metaLead).toBeInTheDocument()
451
+ expect(metaTrail).toBeInTheDocument()
452
+ expect(metaLead?.textContent).toBe('Author Name')
453
+ expect(metaTrail?.textContent).toBe('2023-12-01')
454
+ })
455
+
456
+ test('renders only metaLead when metaTrail not provided', async () => {
457
+ const container = await createCard()
458
+ const card = container.querySelector('pkt-card') as PktCard
459
+ card.metaLead = 'Author Only'
460
+ await card.updateComplete
461
+
462
+ const metadata = card.querySelector('.pkt-card__metadata')
463
+ const metaLead = metadata?.querySelector('.pkt-card__metadata-lead')
464
+ const metaTrail = metadata?.querySelector('.pkt-card__metadata-trail')
465
+
466
+ expect(metadata).toBeInTheDocument()
467
+ expect(metaLead).toBeInTheDocument()
468
+ expect(metaTrail).not.toBeInTheDocument()
469
+ expect(metaLead?.textContent).toBe('Author Only')
470
+ })
471
+
472
+ test('renders only metaTrail when metaLead not provided', async () => {
473
+ const container = await createCard()
474
+ const card = container.querySelector('pkt-card') as PktCard
475
+ card.metaTrail = 'Date Only'
476
+ await card.updateComplete
477
+
478
+ const metadata = card.querySelector('.pkt-card__metadata')
479
+ const metaLead = metadata?.querySelector('.pkt-card__metadata-lead')
480
+ const metaTrail = metadata?.querySelector('.pkt-card__metadata-trail')
481
+
482
+ expect(metadata).toBeInTheDocument()
483
+ expect(metaLead).not.toBeInTheDocument()
484
+ expect(metaTrail).toBeInTheDocument()
485
+ expect(metaTrail?.textContent).toBe('Date Only')
486
+ })
487
+
488
+ test('does not render metadata when neither provided', async () => {
489
+ const container = await createCard()
490
+
491
+ const card = container.querySelector('pkt-card') as PktCard
492
+ await card.updateComplete
493
+
494
+ const metadata = card.querySelector('.pkt-card__metadata')
495
+ expect(metadata).not.toBeInTheDocument()
496
+ })
497
+ })
498
+
499
+ describe('Content placement and structure', () => {
500
+ test('renders content elements in correct order', async () => {
501
+ const container = await createCard(
502
+ 'heading="Test Title" subheading="Test Sub"',
503
+ 'Main content here',
504
+ )
505
+ const card = container.querySelector('pkt-card') as PktCard
506
+ card.tags = [{ text: 'Test Tag' }]
507
+ card.image = { src: '/test.jpg', alt: 'Test' }
508
+ card.metaLead = 'Author'
509
+ card.metaTrail = 'Date'
510
+ await card.updateComplete
511
+
512
+ const article = card.querySelector('article')
513
+ const children = Array.from(article?.children || [])
514
+
515
+ // Should have image first, then wrapper
516
+ expect(children[0]).toHaveClass('pkt-card__image')
517
+ expect(children[1]).toHaveClass('pkt-card__wrapper')
518
+
519
+ const wrapper = children[1]
520
+ const wrapperChildren = Array.from(wrapper?.children || [])
521
+
522
+ // Order within wrapper: tags (top), header, content, metadata
523
+ expect(wrapperChildren[0]).toHaveClass('pkt-card__tags-top')
524
+ expect(wrapperChildren[1]).toHaveClass('pkt-card__header')
525
+ expect(wrapperChildren[2]).toHaveClass('pkt-card__content')
526
+ expect(wrapperChildren[3]).toHaveClass('pkt-card__metadata')
527
+ })
528
+
529
+ test('places tags at bottom when tagPosition is bottom', async () => {
530
+ const container = await createCard('heading="Test Title"', 'Main content')
531
+ const card = container.querySelector('pkt-card') as PktCard
532
+ card.tags = [{ text: 'Test Tag' }]
533
+ card.tagPosition = 'bottom'
534
+ await card.updateComplete
535
+
536
+ const wrapper = card.querySelector('.pkt-card__wrapper')
537
+ const wrapperChildren = Array.from(wrapper?.children || [])
538
+
539
+ // Order: header, content, tags (bottom)
540
+ expect(wrapperChildren[0]).toHaveClass('pkt-card__header')
541
+ expect(wrapperChildren[1]).toHaveClass('pkt-card__content')
542
+ expect(wrapperChildren[2]).toHaveClass('pkt-card__tags-bottom')
543
+ })
544
+ })
545
+
546
+ describe('Accessibility', () => {
547
+ test('has no accessibility violations', async () => {
548
+ const container = await createCard(
549
+ 'heading="Accessible Card" aria-label="Test card"',
550
+ 'Accessible content',
551
+ )
552
+
553
+ const card = container.querySelector('pkt-card') as PktCard
554
+ await card.updateComplete
555
+
556
+ const results = await axe(card)
557
+ expect(results).toHaveNoViolations()
558
+ })
559
+
560
+ test('applies correct ARIA attributes', async () => {
561
+ const container = await createCard('heading="Test"')
562
+ const card = container.querySelector('pkt-card') as PktCard
563
+ card.ariaLabel = 'Custom accessible label'
564
+ await card.updateComplete
565
+
566
+ expect(card.ariaLabel).toBe('Custom accessible label')
567
+
568
+ const article = card.querySelector('article')
569
+ expect(article?.getAttribute('aria-label')).toBe('Custom accessible label')
570
+ })
571
+
572
+ test('falls back to heading for aria-label when no explicit aria-label', async () => {
573
+ const container = await createCard('heading="Default Aria Label"')
574
+
575
+ const card = container.querySelector('pkt-card') as PktCard
576
+ await card.updateComplete
577
+
578
+ const article = card.querySelector('article')
579
+ expect(article?.getAttribute('aria-label')).toBe('Default Aria Label')
580
+ })
581
+
582
+ test('falls back to "kort" when no heading or aria-label', async () => {
583
+ const container = await createCard()
584
+
585
+ const card = container.querySelector('pkt-card') as PktCard
586
+ await card.updateComplete
587
+
588
+ const article = card.querySelector('article')
589
+ expect(article?.getAttribute('aria-label')).toBe('kort')
590
+ })
591
+ })
592
+ })
@@ -10,6 +10,7 @@ import { IPktHeading } from '../heading'
10
10
  import specs from 'componentSpecs/card.json'
11
11
  import '@/components/icon'
12
12
  import '@/components/tag'
13
+ import '@/components/heading'
13
14
  import { IAriaAttributes } from '@/types/aria'
14
15
 
15
16
  export type TCardSkin = 'outlined' | 'outlined-beige' | 'gray' | 'beige' | 'green' | 'blue'
@@ -67,7 +68,29 @@ export class PktCard extends PktElement implements IPktCard {
67
68
  @property({ type: String }) imageShape: TCardImageShape = 'square'
68
69
  @property({ type: Boolean }) openLinkInNewTab: boolean = false
69
70
  @property({ type: String }) padding: TCardPadding = specs.props.padding.default as TCardPadding
70
- @property({ type: String }) skin: TCardSkin = specs.props.skin.default as TCardSkin
71
+
72
+ @property({
73
+ type: String,
74
+ converter: {
75
+ fromAttribute: (value: string | null): TCardSkin => {
76
+ const validSkins = specs.props.skin.type as TCardSkin[]
77
+
78
+ if (value && validSkins.includes(value as TCardSkin)) {
79
+ return value as TCardSkin
80
+ } else {
81
+ if (value && !validSkins.includes(value as TCardSkin)) {
82
+ console.warn(
83
+ `Invalid skin value "${value}". Using default skin "${specs.props.skin.default}".`,
84
+ )
85
+ }
86
+ return specs.props.skin.default as TCardSkin
87
+ }
88
+ },
89
+ toAttribute: (value: TCardSkin): string => value,
90
+ },
91
+ })
92
+ skin: TCardSkin = specs.props.skin.default as TCardSkin
93
+
71
94
  @property({ type: String }) subheading: string = ''
72
95
  @property({ type: String }) tagPosition: 'top' | 'bottom' = 'top'
73
96
  @property({ type: Array }) tags: (Omit<IPktTag, 'closeTag'> & { text: string })[] = []