diff --git a/docs/pages/playground/cards.vue b/docs/pages/playground/cards.vue
new file mode 100644
index 000000000..f1cb46683
--- /dev/null
+++ b/docs/pages/playground/cards.vue
@@ -0,0 +1,277 @@
+
+
+
+
+ Cards QA page
+
+
Also preview live examples on KCardGrid
and KCard
documentation pages.
+
+
+ Horizontal cards with large amount of content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Horizontal cards with small amount of content
+
+
+
+
+
+
+
+
+
+
+ Vertical cards with large amount of content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vertical cards with small amount of content
+
+
+
+
+
+
+
+
+
+
+ Cards when links
+
+
+
+
+
+
+
+ Cards when not links
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/cards/KCard.vue b/lib/cards/KCard.vue
index fee4137ec..a3f9581d4 100644
--- a/lib/cards/KCard.vue
+++ b/lib/cards/KCard.vue
@@ -17,7 +17,9 @@
:class="['card-area', ...cardAreaClasses ]"
:style="{ backgroundColor: $themeTokens.surface }"
>
-
-
- {{ title }}
-
-
-
-
-
-
-
-
+
+ {{ title }}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
in KImg with z-index 1 should cover the placeholder after loaded */
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
- }
-
.title {
display: inline-block; // allows title placeholder in the skeleton card
width: 100%; // allows title placeholder in the skeleton card
@@ -634,57 +603,94 @@
outline: none; // the focus ring is moved to the whole
}
- /************* Manage spaces *************/
+ // Because the title and the areas above and below it
+ // are grouped together in all layoyts, abstract them
+ // into one whole here. Simplifies common spacing
+ // styles as well as layout-specific styles.
+ .around-title {
+ display: flex;
+ flex-direction: column;
+
+ .heading {
+ order: 2;
+ }
+
+ .above-title {
+ order: 1;
+ }
+
+ .below-title {
+ order: 3;
+ }
+ }
+
+ /************* Spacing *************/
+ // first reset
+ .around-title,
.heading,
.above-title,
.below-title,
.footer {
padding: 0;
- margin-top: 0;
- margin-right: $spacer;
- margin-bottom: calc(#{$spacer} / 2);
- margin-left: $spacer;
+ margin: 0;
}
- // when the 'aboveTitle' area is present,
- // apply top margin to it and also set smaller
- // margin between the area and the heading...
- .with-above-title {
- .above-title {
- margin-top: $spacer;
- margin-bottom: calc(#{$spacer} / 2);
- }
+ /* stylelint-disable no-duplicate-selectors */
+ .around-title {
+ padding: $spacer;
}
+ /* stylelint-enable no-duplicate-selectors */
- // ...and when the 'aboveTitle' area is not present,
- // instead apply the top margin to the heading
- .without-above-title {
- .heading {
- margin-top: $spacer;
- }
+ .footer {
+ padding: 0 $spacer $spacer $spacer;
}
- // if there's the 'belowTitle' area present,
- // override the heading's margin to smaller one
- .with-below-title {
- .heading {
- margin-bottom: calc(#{$spacer} / 2);
- }
+ .above-title {
+ padding-bottom: calc(#{$spacer} / 2);
}
+ .below-title {
+ padding-top: calc(#{$spacer} / 2);
+ }
+
+ /************* Thumbnail **************/
+
/* stylelint-disable no-duplicate-selectors */
- .footer {
- margin-top: auto; // push footer to the bottom
+ .thumbnail {
+ position: relative; /* for absolute positioning of .thumbnail-placeholder */
}
/* stylelint-enable no-duplicate-selectors */
+ .thumbnail-placeholder {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 0; /* in KImg with z-index 1 should cover the placeholder after loaded */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ }
+
/************* Layout-specific styles *************/
.vertical-with-large-thumbnail {
+ .upper-card-area {
+ display: flex;
+ flex-direction: column;
+ }
+
.thumbnail {
+ order: 1;
height: 180px;
}
+
+ .around-title {
+ order: 2;
+ }
}
.vertical-with-small-thumbnail {
@@ -698,104 +704,92 @@
}
}
- .horizontal-with-large-thumbnail {
- $thumbnail-width: 40%;
+ .horizontal-with-small-thumbnail {
+ .upper-card-area {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ }
.thumbnail {
- position: absolute;
- width: $thumbnail-width;
- height: 100%;
+ width: 30%;
+ margin-top: $spacer;
+ margin-bottom: $spacer;
}
- .heading,
- .above-title,
- .below-title,
- .footer {
- width: calc(100% - #{$thumbnail-width} - 2 * #{$spacer});
+ .around-title {
+ width: 70%;
}
&.thumbnail-align-left {
- align-items: flex-end;
-
.thumbnail {
- left: 0;
+ order: 1;
+ margin-left: $spacer;
+ }
+
+ .around-title {
+ order: 2;
}
}
&.thumbnail-align-right {
- align-items: flex-start;
-
.thumbnail {
- right: 0;
+ order: 2;
+ margin-right: $spacer;
+ }
+
+ .around-title {
+ order: 1;
}
}
}
- .horizontal-with-small-thumbnail {
- $thumbnail-width: null;
-
- /*
- Coordinates space taken by the thumbnail area and the content area
- next to it more intelligently in browsers that support `clamp()` by:
-
- - Instead of defining 'width', 'min-width', and 'max-width' separately,
- `clamp()` is used with the goal to have the actual thumbnail width
- saved in the single `$thumbnail-width` value.
-
- - The `$thumbnail-width` value is then referenced when calculating
- the remaining space for the content area, ensuring the precise
- distribution of space.
-
- Resolves some issues related to unprecise calculations, most importantly
- this removes the area of empty space between the thumbnail and content areas
- in some card's sizes, wasting space that can be used for card's textual content.
- */
- @mixin clamp-with-fallback($min, $preferred, $max) {
- // fallback for browsers that don't support 'clamp()'
- $thumbnail-width: $preferred;
-
- width: $thumbnail-width;
- min-width: $min;
- max-width: $max;
+ .horizontal-with-large-thumbnail {
+ // override few styles from .horizontal-with-small-thumbnail
+ // to stretch the thumbnail to the full height of the card
- // clamp(1px, 1%, 1px) only used for testing support
- @supports (width: clamp(1px, 1%, 1px)) {
- $thumbnail-width: clamp(#{$min}, #{$preferred}, #{$max});
+ /* stylelint-disable scss/at-extend-no-missing-placeholder */
+ @extend .horizontal-with-small-thumbnail;
+ /* stylelint-enable scss/at-extend-no-missing-placeholder */
- width: $thumbnail-width;
- }
+ &.card-area {
+ position: relative; /* for absolute positioning of .thumbnail */
}
.thumbnail {
- @include clamp-with-fallback(100px, 35%, 120px);
-
position: absolute;
- top: $spacer;
- }
-
- .heading,
- .above-title,
- .below-title {
- width: calc(100% - #{$thumbnail-width} - 3 * #{$spacer});
+ width: 40%;
+ height: 100%;
+ margin-top: 0;
+ margin-bottom: 0;
}
+ .around-title,
.footer {
- width: calc(100% - 2 * #{$spacer});
+ width: 60%;
}
&.thumbnail-align-left {
- align-items: flex-end;
-
.thumbnail {
- left: $spacer;
+ left: 0;
+ margin-left: 0;
+ }
+
+ .around-title,
+ .footer {
+ margin-left: auto;
}
}
&.thumbnail-align-right {
- align-items: flex-start;
-
.thumbnail {
- right: $spacer;
+ right: 0;
+ margin-right: 0;
+ }
+
+ .around-title,
+ .footer {
+ margin-right: auto;
}
}
}
diff --git a/lib/cards/__tests__/KCard.spec.js b/lib/cards/__tests__/KCard.spec.js
index 978fd9a11..a7041dddf 100644
--- a/lib/cards/__tests__/KCard.spec.js
+++ b/lib/cards/__tests__/KCard.spec.js
@@ -30,41 +30,31 @@ describe('KCard', () => {
// Check for:
//
- // li
- // |-- div
- // |--span (visually hidden title text)
- // |-- h[2-6]
- // |--a (with aria-labelledby pointing to the span above)
- // |-- span (with aria-hidden)
+ //
+ // |-- with visually hidden title text
+ // |--
+ // |-- with aria-labelledby pointing to the span with the visually hidden title text
+ // |-- with aria-hidden
//
const el1 = wrapper.find(':first-child');
expect(el1.exists()).toBe(true);
expect(el1.element.tagName.toLowerCase()).toBe('li');
- const el2 = wrapper.find('li > :first-child');
+ const el2 = wrapper.find('li .visuallyhidden');
expect(el2.exists()).toBe(true);
- expect(el2.element.tagName.toLowerCase()).toBe('div');
+ expect(el2.text()).toBe('Test Title');
+ const spanId = el2.attributes('id');
- const el3 = wrapper.find('li > div > :nth-child(1)');
+ const el3 = wrapper.find('li h4');
expect(el3.exists()).toBe(true);
- expect(el3.element.tagName.toLowerCase()).toBe('span');
- expect(el3.classes()).toContain('visuallyhidden');
- expect(el3.text()).toBe('Test Title');
- const spanId = el3.attributes('id');
- const el4 = wrapper.find('li > div > :nth-child(2)');
+ const el4 = wrapper.find(`li h4 [aria-labelledby="${spanId}"]`);
expect(el4.exists()).toBe(true);
- expect(el4.element.tagName.toLowerCase()).toBe('h4');
+ expect(el4.element.tagName.toLowerCase()).toBe('a');
- const el5 = wrapper.find(`li > div > h4 > :first-child`);
+ const el5 = wrapper.find(`li h4 [aria-labelledby="${spanId}"] > :first-child`);
expect(el5.exists()).toBe(true);
- expect(el5.element.tagName.toLowerCase()).toBe('a');
- expect(el5.attributes('aria-labelledby')).toBe(spanId);
-
- const el6 = wrapper.find(`li > div > h4 > a > :first-child`);
- expect(el6.exists()).toBe(true);
- expect(el6.element.tagName.toLowerCase()).toBe('span');
- expect(el6.attributes('aria-hidden')).toBe('true');
+ expect(el5.attributes('aria-hidden')).toBe('true');
});
it(`renders the correct accessibility structure when card is not link`, () => {
@@ -74,42 +64,31 @@ describe('KCard', () => {
// Check for:
//
- // li
- // |-- div
- // |--span (visually hidden title text)
- // |-- h[2-6]
- // |--span (focusable and with aria-labelledby pointing to the span above)
- // |-- span (with aria-hidden)
+ //
+ // |-- with visually hidden title text
+ // |--
+ // |-- with aria-labelledby pointing to the span with the visually hidden title text
+ // |-- with aria-hidden
//
const el1 = wrapper.find(':first-child');
expect(el1.exists()).toBe(true);
expect(el1.element.tagName.toLowerCase()).toBe('li');
- const el2 = wrapper.find('li > :first-child');
+ const el2 = wrapper.find('li .visuallyhidden');
expect(el2.exists()).toBe(true);
- expect(el2.element.tagName.toLowerCase()).toBe('div');
+ expect(el2.text()).toBe('Test Title');
+ const spanId = el2.attributes('id');
- const el3 = wrapper.find('li > div > :nth-child(1)');
+ const el3 = wrapper.find('li h4');
expect(el3.exists()).toBe(true);
- expect(el3.element.tagName.toLowerCase()).toBe('span');
- expect(el3.classes()).toContain('visuallyhidden');
- expect(el3.text()).toBe('Test Title');
- const spanId = el3.attributes('id');
- const el4 = wrapper.find('li > div > :nth-child(2)');
+ const el4 = wrapper.find(`li h4 [aria-labelledby="${spanId}"]`);
expect(el4.exists()).toBe(true);
- expect(el4.element.tagName.toLowerCase()).toBe('h4');
+ expect(el4.element.tagName.toLowerCase()).toBe('span');
- const el5 = wrapper.find(`li > div > h4 > :first-child`);
+ const el5 = wrapper.find(`li h4 [aria-labelledby="${spanId}"] > :first-child`);
expect(el5.exists()).toBe(true);
- expect(el5.element.tagName.toLowerCase()).toBe('span');
- expect(el5.attributes('aria-labelledby')).toBe(spanId);
- expect(el5.attributes('tabindex')).toBe('0');
-
- const el6 = wrapper.find(`li > div > h4 > span > :first-child`);
- expect(el6.exists()).toBe(true);
- expect(el6.element.tagName.toLowerCase()).toBe('span');
- expect(el6.attributes('aria-hidden')).toBe('true');
+ expect(el5.attributes('aria-hidden')).toBe('true');
});
describe('on long click', () => {