Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Use a without href with role=link for (disabled) links without …
Browse files Browse the repository at this point in the history
…`href`
  • Loading branch information
obulat committed Sep 28, 2022
1 parent 8f643b0 commit 40a14b8
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 88 deletions.
1 change: 0 additions & 1 deletion src/components/VAudioTrack/layouts/VFullLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
<i18n as="span" path="audio-track.creator" class="font-semibold">
<template #creator>
<VLink
v-if="audio.creator_url"
class="rounded-sm p-px focus:outline-none focus:ring focus:ring-pink"
:href="audio.creator_url"
>
Expand Down
20 changes: 14 additions & 6 deletions src/components/VContentLink/VContentLink.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
<template>
<!-- We 'disable' the link when there are 0 results by using a div and setting aria-disabled. -->
<VContentLinkWrapper :results-count="resultsCount" :to="to">
<!-- We 'disable' the link when there are 0 results by removing the href and setting aria-disabled. -->
<VLink
:href="hasResults ? to : undefined"
class="flex w-full flex-col items-start overflow-hidden rounded-sm border border-dark-charcoal/20 bg-white py-4 ps-4 pe-12 md:flex-row md:items-center md:justify-between md:p-6"
:class="
hasResults
? ' text-dark-charcoal hover:bg-dark-charcoal hover:text-white hover:no-underline focus:border-tx focus:outline-none focus-visible:ring focus-visible:ring-pink'
: 'cursor-not-allowed text-dark-charcoal/40'
"
@keydown.native.shift.tab.exact="$emit('shift-tab', $event)"
>
<div class="flex flex-col items-start md:flex-row md:items-center">
<VIcon :icon-path="iconPath" />
<p class="hidden pt-1 font-semibold md:block md:pt-0 md:text-2xl md:ps-2">
Expand All @@ -11,7 +20,7 @@
</p>
</div>
<span class="text-sr">{{ resultsCountLabel }}</span>
</VContentLinkWrapper>
</VLink>
</template>

<script lang="ts">
Expand All @@ -23,7 +32,7 @@ import { AUDIO, IMAGE, SupportedMediaType } from '~/constants/media'
import { defineEvent } from '~/types/emits'
import VIcon from '~/components/VIcon/VIcon.vue'
import VContentLinkWrapper from '~/components/VContentLink/VContentLinkWrapper.vue'
import VLink from '~/components/VLink.vue'
import audioIcon from '~/assets/icons/audio-wave.svg'
import imageIcon from '~/assets/icons/image.svg'
Expand All @@ -35,7 +44,7 @@ const iconMapping = {
export default defineComponent({
name: 'VContentLink',
components: { VIcon, VContentLinkWrapper },
components: { VIcon, VLink },
props: {
/**
* One of the media types supported.
Expand All @@ -57,7 +66,6 @@ export default defineComponent({
*/
to: {
type: String,
required: true,
},
},
emits: {
Expand Down
58 changes: 0 additions & 58 deletions src/components/VContentLink/VContentLinkWrapper.vue

This file was deleted.

35 changes: 17 additions & 18 deletions src/components/VLink.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
<!-- eslint-disable vue/no-restricted-syntax -->
<template>
<NuxtLink
v-if="linkComponent === 'NuxtLink'"
v-if="isNuxtLink"
:class="{ 'inline-flex flex-row items-center gap-2': showExternalIcon }"
:to="linkTo"
v-on="$listeners"
@click.native="$emit('click', $event)"
>
<slot /><VIcon
v-if="showExternalIcon && !isInternal"
:icon-path="externalLinkIcon"
class="inline-block"
:size="4"
rtl-flip
/>
<slot />
</NuxtLink>
<a
v-else-if="linkComponent === 'a'"
v-else
:href="href"
target="_blank"
rel="noopener noreferrer"
:role="href ? undefined : 'link'"
:aria-disabled="!href"
:class="{ 'inline-flex flex-row items-center gap-2': showExternalIcon }"
v-on="$listeners"
>
Expand All @@ -35,9 +31,9 @@

<script lang="ts">
/**
* This is a wrapper component for all links. If a link is dynamically generated and doesn't have
* an `href` prop (as the links for detail pages when the image detail hasn't loaded yet),
* it is rendered as a `span`.
* This is a wrapper component for all links. If `href` prop is undefined,
* the link will be rendered as a disabled: an `<a>` element without `href`
* attribute and with `role="link"` and `aria-disabled="true"` attributes.
* Links with `href` starting with `/` are treated as internal links.
*
* Internal links use `NuxtLink` component with `to` attribute set to `localePath(href)`
Expand All @@ -55,8 +51,9 @@ export default defineComponent({
props: {
href: {
type: String,
required: true,
validator: (v: string) => v.length > 0,
required: false,
validator: (v: string | undefined) =>
(typeof v === 'string' && v.length > 0) || typeof v === 'undefined',
},
/**
* whether to render the external link icon next to links that point away
Expand All @@ -72,22 +69,24 @@ export default defineComponent({
function checkHref(
p: typeof props
): p is { href: string; showExternalIcon: boolean } {
return !['', '#'].includes(p.href)
return typeof p.href === 'string' && !['', '#'].includes(p.href)
}
const hasHref = computed(() => checkHref(props))
const isInternal = computed(
() => hasHref.value && props.href?.startsWith('/')
)
const linkComponent = computed(() => (isInternal.value ? 'NuxtLink' : 'a'))
const isNuxtLink = computed(() => hasHref.value && isInternal.value)
let linkTo = computed(() =>
isInternal.value ? app?.localePath(props.href) ?? props.href : null
checkHref(props) && isInternal.value
? app?.localePath(props.href) ?? props.href
: null
)
return {
linkTo,
linkComponent,
isNuxtLink,
isInternal,
externalLinkIcon,
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/VSourcesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{{ provider.display_name }}
</td>
<td class="truncate font-semibold">
<VLink v-if="provider.source_url" :href="provider.source_url">
<VLink :href="provider.source_url">
{{ cleanSourceUrlForPresentation(provider.source_url) }}
</VLink>
</td>
Expand Down
1 change: 0 additions & 1 deletion src/pages/image/_id.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
class="flex flex-row flex-wrap justify-between md:mt-6 md:flex-row-reverse"
>
<VButton
v-if="!!image.foreign_landing_url"
as="VLink"
:href="image.foreign_landing_url"
class="description-bold md:heading-6 mb-4 w-full flex-initial md:mb-0 md:w-max"
Expand Down
1 change: 0 additions & 1 deletion src/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@
appear
>
<VLink
v-if="image.url"
:href="image.url"
class="homepage-image block aspect-square h-30 w-30 rounded-full lg:m-[2vh] lg:h-auto lg:w-auto"
:style="{ '--transition-index': `${index * 0.05}s` }"
Expand Down
3 changes: 1 addition & 2 deletions test/unit/specs/components/v-content-link.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ describe('VContentLink', () => {
expect(btn).not.toHaveAttribute('aria-disabled')
})

// TODO: Fix the component disabled a11y
xit('is disabled when there are no results', () => {
it('is disabled when there are no results', () => {
options.props.resultsCount = 0
render(VContentLink, options)
const btn = screen.getByRole('link')
Expand Down

0 comments on commit 40a14b8

Please sign in to comment.