Skip to content

Commit

Permalink
fix(Pagination): ignore clicks if pagination button is disabled (#1415)
Browse files Browse the repository at this point in the history
  • Loading branch information
andreww2012 authored Nov 7, 2024
1 parent 2f8342a commit fdb13b1
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 23 deletions.
68 changes: 68 additions & 0 deletions packages/radix-vue/src/Pagination/Pagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,74 @@ describe('given default Pagination', () => {
})
})

const ALL_PAGINATION_BUTTONS_AS_A_PROPS = {
first: { as: 'a' },
prev: { as: 'a' },
listItem: { as: 'a' },
next: { as: 'a' },
last: { as: 'a' },
}

describe('given Pagination with <a> as buttons', () => {
let wrapper: VueWrapper<InstanceType<typeof Pagination>>

beforeEach(() => {
document.body.innerHTML = ''
wrapper = mount(Pagination, { attachTo: document.body, props: { ...ALL_PAGINATION_BUTTONS_AS_A_PROPS } })
})

it('should pass axe accessibility tests', async () => {
expect(await axe(wrapper.element)).toHaveNoViolations()
})

it('should not unselect page 1 after clicking on Prev Page trigger', async () => {
await wrapper.find('[aria-label="Previous Page"]').trigger('click')
expect(wrapper.find('[aria-label="Page 1"]').attributes('data-selected')).toBe('true')
})

it('should not unselect last page after clicking on Next Page trigger', async () => {
await wrapper.find('[aria-label="Last Page"]').trigger('click')
const lastPageLabel = wrapper.find('[data-selected="true"]').attributes('aria-label')
await wrapper.find('[aria-label="Next Page"]').trigger('click')
expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(lastPageLabel)
})
})

describe('given Pagination with <a> as buttons and disabled', () => {
let wrapper: VueWrapper<InstanceType<typeof Pagination>>

const INITIAL_PAGE = 2 // Do not set to first or last page

beforeEach(async () => {
document.body.innerHTML = ''
wrapper = mount(Pagination, { attachTo: document.body, props: { ...ALL_PAGINATION_BUTTONS_AS_A_PROPS } })
await wrapper.find(`[aria-label="Page ${INITIAL_PAGE}"]`).trigger('click')
wrapper.setProps({ root: { disabled: true } })
})

it('should pass axe accessibility tests', async () => {
expect(await axe(wrapper.element)).toHaveNoViolations()
})

it('should ignore clicking on First Page trigger', async () => {
await wrapper.find('[aria-label="First Page"]').trigger('click')

expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(`Page ${INITIAL_PAGE}`)
})

it('should ignore clicking on Last Page trigger', async () => {
await wrapper.find('[aria-label="Last Page"]').trigger('click')

expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(`Page ${INITIAL_PAGE}`)
})

it('should ignore clicking on any non-selected page', async () => {
await wrapper.find('[aria-label="Page 1"]').trigger('click')

expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(`Page ${INITIAL_PAGE}`)
})
})

describe('given show-edges Pagination', () => {
let wrapper: VueWrapper<InstanceType<typeof Pagination>>

Expand Down
7 changes: 5 additions & 2 deletions packages/radix-vue/src/Pagination/PaginationFirst.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ export interface PaginationFirstProps extends PrimitiveProps {}
</script>

<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from '@/Primitive'
import { injectPaginationRootContext } from './PaginationRoot.vue'
const props = withDefaults(defineProps<PaginationFirstProps>(), { as: 'button' })
const rootContext = injectPaginationRootContext()
useForwardExpose()
const disabled = computed((): boolean => rootContext.page.value === 1 || rootContext.disabled.value)
</script>

<template>
<Primitive
v-bind="props"
aria-label="First Page"
:type="as === 'button' ? 'button' : undefined"
:disabled="rootContext.page.value === 1 || rootContext.disabled.value"
@click="rootContext.onPageChange(1)"
:disabled
@click="!disabled && rootContext.onPageChange(1)"
>
<slot>First page</slot>
</Primitive>
Expand Down
7 changes: 5 additions & 2 deletions packages/radix-vue/src/Pagination/PaginationLast.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ export interface PaginationLastProps extends PrimitiveProps {}
</script>

<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from '@/Primitive'
import { injectPaginationRootContext } from './PaginationRoot.vue'
const props = withDefaults(defineProps<PaginationLastProps>(), { as: 'button' })
const rootContext = injectPaginationRootContext()
useForwardExpose()
const disabled = computed((): boolean => rootContext.page.value === rootContext.pageCount.value || rootContext.disabled.value)
</script>

<template>
<Primitive
v-bind="props"
aria-label="Last Page"
:type="as === 'button' ? 'button' : undefined"
:disabled="rootContext.page.value === rootContext.pageCount.value || rootContext.disabled.value"
@click="rootContext.onPageChange(rootContext.pageCount.value)"
:disabled
@click="!disabled && rootContext.onPageChange(rootContext.pageCount.value)"
>
<slot>Last page</slot>
</Primitive>
Expand Down
8 changes: 5 additions & 3 deletions packages/radix-vue/src/Pagination/PaginationListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ export interface PaginationListItemProps extends PrimitiveProps {
</script>

<script setup lang="ts">
import { Primitive } from '@/Primitive'
import { computed } from 'vue'
import { Primitive } from '@/Primitive'
import { injectPaginationRootContext } from './PaginationRoot.vue'
const props = withDefaults(defineProps<PaginationListItemProps>(), { as: 'button' })
useForwardExpose()
const rootContext = injectPaginationRootContext()
const isSelected = computed(() => rootContext.page.value === props.value)
const disabled = computed((): boolean => rootContext.disabled.value)
</script>

<template>
Expand All @@ -27,9 +29,9 @@ const isSelected = computed(() => rootContext.page.value === props.value)
:aria-label="`Page ${value}`"
:aria-current="isSelected ? 'page' : undefined"
:data-selected="isSelected ? 'true' : undefined"
:disabled="rootContext.disabled.value"
:disabled
:type="as === 'button' ? 'button' : undefined"
@click="rootContext.onPageChange(value)"
@click="!disabled && rootContext.onPageChange(value)"
>
<slot>{{ value }}</slot>
</Primitive>
Expand Down
7 changes: 5 additions & 2 deletions packages/radix-vue/src/Pagination/PaginationNext.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ export interface PaginationNextProps extends PrimitiveProps {}
</script>

<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from '@/Primitive'
import { injectPaginationRootContext } from './PaginationRoot.vue'
const props = withDefaults(defineProps<PaginationNextProps>(), { as: 'button' })
useForwardExpose()
const rootContext = injectPaginationRootContext()
const disabled = computed((): boolean => rootContext.page.value === rootContext.pageCount.value || rootContext.disabled.value)
</script>

<template>
<Primitive
v-bind="props"
aria-label="Next Page"
:type="as === 'button' ? 'button' : undefined"
:disabled="rootContext.page.value === rootContext.pageCount.value || rootContext.disabled.value"
@click="rootContext.onPageChange(rootContext.page.value + 1)"
:disabled
@click="!disabled && rootContext.onPageChange(rootContext.page.value + 1)"
>
<slot>Next page</slot>
</Primitive>
Expand Down
7 changes: 5 additions & 2 deletions packages/radix-vue/src/Pagination/PaginationPrev.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ export interface PaginationPrevProps extends PrimitiveProps {}
</script>

<script setup lang="ts">
import { computed } from 'vue'
import { Primitive } from '@/Primitive'
import { injectPaginationRootContext } from './PaginationRoot.vue'
const props = withDefaults(defineProps<PaginationPrevProps>(), { as: 'button' })
useForwardExpose()
const rootContext = injectPaginationRootContext()
const disabled = computed((): boolean => rootContext.page.value === 1 || rootContext.disabled.value)
</script>

<template>
<Primitive
v-bind="props"
aria-label="Previous Page"
:type="as === 'button' ? 'button' : undefined"
:disabled="rootContext.page.value === 1 || rootContext.disabled?.value"
@click="rootContext.onPageChange(rootContext.page.value - 1)"
:disabled
@click="!disabled && rootContext.onPageChange(rootContext.page.value - 1)"
>
<slot>Prev page</slot>
</Primitive>
Expand Down
37 changes: 25 additions & 12 deletions packages/radix-vue/src/Pagination/story/_Pagination.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
<script setup lang="ts">
import { PaginationEllipsis, PaginationFirst, PaginationLast, PaginationList, PaginationListItem, PaginationNext, PaginationPrev, PaginationRoot, type PaginationRootProps } from '..'
import { computed } from 'vue'
import { PaginationEllipsis, type PaginationEllipsisProps, PaginationFirst, type PaginationFirstProps, PaginationLast, type PaginationLastProps, PaginationList, PaginationListItem, type PaginationListItemProps, type PaginationListProps, PaginationNext, type PaginationNextProps, PaginationPrev, type PaginationPrevProps, PaginationRoot, type PaginationRootProps } from '..'
const props = withDefaults(defineProps<{
total?: PaginationRootProps['total']
showEdges?: PaginationRootProps['showEdges']
}>(), {
total: 100,
})
const props = defineProps<{
root?: PaginationRootProps
list?: PaginationListProps
first?: PaginationFirstProps
prev?: PaginationPrevProps
listItem?: Partial<PaginationListItemProps>
ellipsis?: PaginationEllipsisProps
next?: PaginationNextProps
last?: PaginationLastProps
}>()
const rootProps = computed(() => ({ total: 100, ...props.root }))
</script>

<template>
<PaginationRoot v-bind="props">
<PaginationRoot v-bind="rootProps">
<PaginationList
v-slot="{ items }"
v-bind="props.list"
class="flex items-center gap-2 "
>
<PaginationFirst />
<PaginationPrev />
<PaginationFirst v-bind="props.first" />
<PaginationPrev v-bind="props.prev" />
<template v-for="(page, index) in items">
<PaginationListItem
v-if="page.type === 'page'"
:key="index"
class="border rounded px-4 py-2 data-[selected]:bg-grass8"
:value="page.value"
v-bind="props.listItem"
>
{{ page.value }}
</PaginationListItem>
Expand All @@ -31,12 +43,13 @@ const props = withDefaults(defineProps<{
:key="page.type"
:index="index"
class="border rounded px-4 py-2 "
v-bind="props.ellipsis"
>
&#8230;
</PaginationEllipsis>
</template>
<PaginationNext />
<PaginationLast />
<PaginationNext v-bind="props.next" />
<PaginationLast v-bind="props.last" />
</PaginationList>
</PaginationRoot>
</template>

0 comments on commit fdb13b1

Please sign in to comment.