Skip to content

Commit

Permalink
Make navigation on card click optional
Browse files Browse the repository at this point in the history
and add a click event. This allows for
a custom handler.
  • Loading branch information
MisRob committed Nov 13, 2024
1 parent 22a4e58 commit c49c1a3
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 79 deletions.
72 changes: 50 additions & 22 deletions docs/pages/kcard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
{ text: 'KCard and KCardGrid', href: '#k-card-and-grid' },
{ text: 'Title', href: '#title' },
{ text: 'Accessibility', href: '#a11y' },
{ text: 'Navigation', href: '#navigation' },
{ text: 'Click event and navigation', href: '#click-navigation' },
{ text: 'Layout', href: '#layout' },
{ text: 'Responsiveness', href: '#responsiveness' },
{ text: 'Content slots', href: '#content-slots' },
Expand Down Expand Up @@ -214,28 +214,56 @@
<p><em>Always test semantics, accessibility, and right-to-left of the final cards.</em></p>

<h3>
Navigation
<DocsAnchorTarget anchor="#navigation" />
Click event and navigation
<DocsAnchorTarget anchor="#click-navigation" />
</h3>

<p><code>KCard</code>'s entire area is clickable, navigating to a target provided via the <code>to</code> prop as a regular Vue route object. <DocsToggleButton contentId="more-navigation" /></p>

<DocsToggleContent id="more-navigation">
<!-- eslint-disable -->
<DocsShowCode language="html">
<KCardGrid ...>
<KCard
:to="{ name: 'NamedRoute' }"
...
/>
<KCard
:to="{ path: '/kcard' }"
...
/>
</KCardGrid>
</DocsShowCode>
<!-- eslint-enable -->
</DocsToggleContent>
<p><code>KCard</code>'s entire area is clickable.</p>

<p>You can use the <code>to</code> prop to navigate to a URL when the card is clicked.</p>

<DocsShowCode language="html">
<KCardGrid ...>
<KCard
...
:to="{ name: 'NamedRoute' }"
/>
<KCard
...
:to="{ path: '/kcard' }"
/>
</KCardGrid>
</DocsShowCode>

<p>Listen to the <code>click</code> event to perform a custom action (whether or not the <code>to</code> prop is used).</p>

<DocsShowCode language="html">
<KCardGrid ...>
<KCard
...
@click="onClick"
/>
<KCard
...
:to="{ path: '/kcard' }"
@click="onClick"
/>
</KCardGrid>
</DocsShowCode>

<!-- eslint-disable -->
<DocsShowCode language="javascript">
export default {
methods() {
onClick() {
console.log('Card clicked');
}
},
};
</DocsShowCode>
<!-- eslint-enable -->

<p>Note that long clicks are ignored to allow for text selection.</p>

<p>See <DocsInternalLink text="Interactive elements" href="#interactive-elements" /> to learn how to disable card navigation in favor of a custom handler when elements like buttons are rendered within a card.</p>

Expand Down Expand Up @@ -563,7 +591,7 @@
<DocsAnchorTarget anchor="#interactive-elements" />
</h3>

<p>When adding interactive elements like buttons to a card via slots, apply the <code>.stop</code> event modifier to their <code>@click</code> event to prevent the card from navigating away when clicked.</p>
<p>When adding interactive elements like buttons to a card via slots, apply the <code>.stop</code> event modifier to their <code>@click</code> event to prevent the card <DocsInternalLink text="click event and navigation" href="#click-navigation" />.</p>

<p><em>This applies to all slot content, but considering accessibility is especially important with interactive elements.</em> For instance, <code>ariaLabel</code> is applied to the bookmark icon button in the following example so that screenreaders can communicate its purpose. In production, more work would be needed to indicate the bookmark's toggled state. Always assess on a case-by-case basis.</p>

Expand Down
82 changes: 56 additions & 26 deletions lib/cards/KCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@
selecting card's content in `onClick`)
-->
<router-link
v-if="to"
event=""
:to="to"
draggable="false"
class="link"
@focus.native="onLinkFocus"
@blur.native="onLinkBlur"
class="title"
@focus.native="onTitleFocus"
@blur.native="onTitleBlur"
>
<!-- @slot Optional slot section containing the title contents, should not contain a heading element. -->
<slot
Expand All @@ -54,6 +55,31 @@
:maxLines="titleMaxLines"
/>
</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.-->
<span
v-else
tabindex="0"
data-focus="true"
class="title"
@focus="onTitleFocus"
@blur="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>
</component>

<div
Expand Down Expand Up @@ -171,14 +197,6 @@
};
},
props: {
/**
* A Vue route object that defines
* where the card click should navigate.
*/
to: {
type: Object,
required: true,
},
/**
* HTML heading level in range (h2 - h6) for the title
*/
Expand All @@ -194,6 +212,15 @@
}
},
},
/**
* A Vue route object. If provided, card click
* will navigate to the target.
*/
to: {
type: Object,
required: false,
default: null,
},
/**
* Card title
*/
Expand Down Expand Up @@ -293,13 +320,13 @@
return {
mouseDownTime: 0,
ThumbnailDisplays,
isLinkFocused: false,
isTitleFocused: false,
thumbnailError: false,
};
},
computed: {
focusStyle() {
return this.isLinkFocused ? this.$coreOutline : {};
return this.isTitleFocused ? this.$coreOutline : {};
},
hasAboveTitleArea() {
return this.$slots.aboveTitle || this.preserveAboveTitle;
Expand Down Expand Up @@ -436,23 +463,26 @@
}
},
methods: {
onLinkFocus() {
onTitleFocus() {
if (this.isSkeleton) {
return;
}
this.isLinkFocused = true;
this.isTitleFocused = true;
},
onLinkBlur() {
this.isLinkFocused = false;
onTitleBlur() {
this.isTitleFocused = false;
},
onThumbnailError() {
this.thumbnailError = true;
},
navigate() {
if (this.isSkeleton) {
return;
clickHandler() {
/**
* Emitted when a card is clicked
*/
this.$emit('click');
if (this.to) {
this.$router.push(this.to);
}
this.$router.push(this.to);
},
onFocus(e) {
/**
Expand All @@ -467,7 +497,7 @@
this.$emit('hover', e);
},
onEnter() {
this.navigate();
this.clickHandler();
},
onMouseDown() {
this.mouseDownTime = new Date().getTime();
Expand All @@ -482,10 +512,10 @@
// Calculate the time difference between the mouse button press and release.
// If the time difference is greater than or equal to 200 milliseconds,
// it means that the mouse button was pressed and held for a longer time,
// which is not typically interpreted as a click event. Do not navigate
// in such case.
// which is not typically interpreted as a click event so do not run
// the click handler.
if (mouseUpTime - this.mouseDownTime < 200) {
this.navigate();
this.clickHandler();
} else {
return;
}
Expand Down Expand Up @@ -581,7 +611,7 @@
height: 100%;
}
.link {
.title {
display: inline-block; // allows title placeholder in the skeleton card
width: 100%; // allows title placeholder in the skeleton card
color: inherit;
Expand Down
1 change: 0 additions & 1 deletion lib/cards/SkeletonCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
Their logic is disabled in skeleton mode.
-->
<KCard
:to="{ path: null }"
:headingLevel="2"
isSkeleton
aria-hidden="true"
Expand Down
Loading

0 comments on commit c49c1a3

Please sign in to comment.