Skip to content

Commit

Permalink
Make title prop required
Browse files Browse the repository at this point in the history
and allow title customization
via a scoped slot.

Contains related work to ensure reliable
screen readers announcements
no matter whether card is link or no,
and no matter whether the title is
customized via the title slot.
  • Loading branch information
MisRob committed Nov 20, 2024
1 parent 6ecb5f9 commit a087d25
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 114 deletions.
7 changes: 2 additions & 5 deletions docs/pages-components/DocsKCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
<slot name="aboveTitle"></slot>
</template>

<template v-if="$slots.title" #title>
<slot name="title"></slot>
<template v-if="$scopedSlots.title" #title="{ titleText }">
<slot name="title" :titleText="titleText"></slot>
</template>

<template v-if="$slots.belowTitle" #belowTitle>
Expand Down Expand Up @@ -152,9 +152,6 @@
if (this.title) {
return this.title;
}
if (this.$slots.title) {
return null;
}
return `${this.prependTitle} Learn everything about hummingbirds: their habitats, feeding patterns, and stunning flight abilities`;
},
Expand Down
99 changes: 75 additions & 24 deletions docs/pages/kcard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
:orientation="windowBreakpoint > 2 ? 'horizontal' : 'vertical'"
thumbnailDisplay="large"
thumbnailAlign="right"
prependTitle="(2)"
prependTitle="(1)"
showProgressInFooter
/>
</KCardGrid>
Expand All @@ -33,6 +33,7 @@
</li>
<li>Set a correct heading level (<DocsInternalLink text="Title" href="#title" />)</li>
<li>Ensure each card title is unique within a card grid (<DocsInternalLink text="Title" href="#title" />)</li>
<li>Do not use a heading element within the <code>title</code> slot</li>
<li>Ensure content provided via slots is accessible (<DocsInternalLink text="Accessibility" href="#a11y" />)</li>
<li>Even if a thumbnail image is available, provide a placeholder element (<DocsInternalLink text="Placeholder" href="#thumbnail-placeholder" />)</li>
<li>If using selection controls, use pre-defined labels (<DocsInternalLink text="Selection controls" href="#selection-controls" />)</li>
Expand Down Expand Up @@ -148,7 +149,7 @@
<DocsAnchorTarget anchor="#title" />
</h3>

<p><em>Use the <code>title</code> prop to assign an unique title to each card in a grid, and the <code>headingLevel</code> prop to set the heading level on it. The level needs to correspond to the surrounding context.</em> <DocsToggleButton contentId="more-heading-level" /></p>
<p><em>Always use the <code>title</code> prop to assign an unique title to each card in a grid, and the <code>headingLevel</code> prop to set the heading level on it. The level needs to correspond to the surrounding context.</em> <DocsToggleButton contentId="more-heading-level" /></p>

<DocsToggleContent id="more-heading-level">
<p>Examples:</p>
Expand All @@ -159,40 +160,79 @@
</ul>
</DocsToggleContent>

<p>The <code>titleMaxLines</code> prop can be used to truncate the title to a set number of lines.</p>
<p>The scoped <code>title</code> slot with its <code>titleText</code> attribute can be used to customize the title.</p>

<DocsShow block :style="{ maxWidth: '600px' }">
<KCardGrid
layout="1-1-1"
:skeletonsConfig="skeletonsConfig9"
:loading="loading"
>
<DocsKCard
:headingLevel="3"
orientation="horizontal"
thumbnailDisplay="small"
thumbnailAlign="right"
prependTitle="(1)"
hideFooter
>
<template #title="{ titleText }">
<KLabeledIcon icon="readSolid">
<KTextTruncator
:text="titleText"
:maxLines="1"
/>
</KLabeledIcon>
</template>
</DocsKCard>
</KCardGrid>
</DocsShow>

<!-- eslint-disable -->
<DocsShowCode language="html">
<template>
<KCardGrid>
<KCard
:headingLevel="3"
title="(1) Learn everything about hummingbirds: their habitats, feeding patterns, and stunning flight abilities"
...
>
<template #title="{ titleText }">
<KLabeledIcon icon="readSolid">
<KTextTruncator
:text="titleText"
:maxLines="1"
/>
</KLabeledIcon>
</template>
</KCard>
</KCardGrid>
</template>
</DocsShowCode>
<!-- eslint-enable -->

<p>For more customization, the <code>title</code> slot can be used. Provide only a title text to the slot without wrapping it in a heading element to avoid duplicate headings in the markup output. <DocsToggleButton contentId="more-title-slot" /></p>
<p><em>Do not use a heading element within the <code>title</code> slot to avoid duplicate headings in the markup output.</em><code>KCard</code> already handles a heading element internally.<DocsToggleButton contentId="more-title-slot" /></p>

<DocsToggleContent id="more-title-slot">
<DocsDoNot>
<template #do>
<!-- eslint-disable -->
<DocsShowCode language="html">
<template>
<KCardGrid>
<KCard
:headingLevel="3"
...
>
<template #title>
Card title
</template>
</KCard>
</KCardGrid>
</template>
</DocsShowCode>
<!-- eslint-enable -->
</template>
<template #not>
<DocsShowCode language="html">
<template>
<KCardGrid>
<KCard
:headingLevel="3"
title="(1) Learn everything about hummingbirds"
...
>
<template #title>
<h3>Card title</h3>
<template #title="{ titleText }">
<h3>
<KLabeledIcon icon="readSolid">
<KTextTruncator
:text="titleText"
:maxLines="2"
/>
</KLabeledIcon>
</h3>
</template>
</KCard>
</KCardGrid>
Expand All @@ -202,6 +242,8 @@
</DocsDoNot>
</DocsToggleContent>

<p>The <code>titleMaxLines</code> prop can be used to truncate the title to a set number of lines.</p>

<h3>
Accessibility
<DocsAnchorTarget anchor="#a11y" />
Expand Down Expand Up @@ -889,6 +931,15 @@
height: '180px',
},
],
skeletonsConfig9: [
{
breakpoints: [0, 1, 2, 3, 4, 5, 6, 7],
orientation: 'horizontal',
thumbnailDisplay: 'small',
thumbnailAlign: 'right',
height: '130px',
},
],
};
},
mounted() {
Expand Down
57 changes: 31 additions & 26 deletions lib/cards/KCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,19 @@
:class="['card-area', ...cardAreaClasses ]"
:style="{ backgroundColor: $themeTokens.surface }"
>
<!--
Utilizing `visuallyhidden`, `aria-labelledby`,
and `aria-hidden` to ensure:
- More reliable output for some screen readers
in both link and non-link cards
- Prevents undesired screen reader announcements
when title is customized via the `title` slot
-->
<span :id="`card-title-${_uid}`" class="visuallyhidden">
{{ title }}
</span>
<component
:is="headingElement"
v-if="title || $slots.title"
class="heading"
:style="{ color: $themeTokens.text }"
>
Expand All @@ -41,46 +51,46 @@
:to="to"
draggable="false"
class="title"
:aria-labelledby="`card-title-${_uid}`"
@focus.native="onTitleFocus"
@blur.native="onTitleBlur"
>
<!-- @slot Optional slot section containing the title contents, should not contain a heading element. -->
<slot
v-if="$slots.title"
name="title"
></slot>
<KTextTruncator
v-else
:text="title"
:maxLines="titleMaxLines"
/>
<span aria-hidden="true">
<!-- @slot A scoped slot via which the `title` can be customized. Provides the `titleText` attribute.-->
<slot
v-if="$scopedSlots.title"
name="title"
:titleText="title"
></slot>
<KTextTruncator
v-else
:text="title"
:maxLines="titleMaxLines"
/>
</span>
</router-link>
<!--
Set tabindex to 0 to make title focusable so we
can use the same focus ring logic like when title
is a router-link. Relatedly set data-focus so that
the trackInputModality can set modality to keyboard
to make the focus ring display correctly.
`aria-label` is needed in this mode otherwise some
screenreaders don't announce the text in KTextTruncator,
for mysterious reasons (likely some internal screenreader logic)
to make the focus ring display correctly
-->
<span
v-else
tabindex="0"
data-focus="true"
class="title"
:aria-label="title"
:aria-labelledby="`card-title-${_uid}`"
@focus="onTitleFocus"
@blur="onTitleBlur"
>
<!-- since we're using `aria-label`, hide all unnecessary elements -->
<span aria-hidden="true">
<!-- @slot Optional slot section containing the title contents, should not contain a heading element. -->
<!-- @slot A scoped slot via which the `title` can be customized. Provides the `titleText` attribute. -->
<slot
v-if="$slots.title"
v-if="$scopedSlots.title"
name="title"
:titleText="title"
></slot>
<KTextTruncator
v-else
Expand Down Expand Up @@ -235,7 +245,7 @@
*/
title: {
type: String,
required: false,
required: true,
default: null,
},
/**
Expand Down Expand Up @@ -466,11 +476,6 @@
return {};
},
},
mounted() {
if (!this.$slots.title && !this.title) {
console.error(`[KCard] provide title via 'title' slot or prop`);
}
},
methods: {
onTitleFocus() {
if (this.isSkeleton) {
Expand Down
1 change: 1 addition & 0 deletions lib/cards/SkeletonCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
isSkeleton
aria-hidden="true"
class="skeleton-card"
title="_"
:style="{ height: height }"
:orientation="orientation"
:thumbnailDisplay="thumbnailDisplay"
Expand Down
Loading

0 comments on commit a087d25

Please sign in to comment.