Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions src/runtime/components/Pagination.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ import { useForwardProps } from '../composables/useForwardProps'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#imports'
import { useComponentProps } from '../composables/useComponentProps'
import { useLocale } from '../composables/useLocale'
import { tv } from '../utils/tv'
import UButton from './Button.vue'

Expand All @@ -129,19 +128,18 @@ const slots = defineSlots<PaginationSlots>()

const props = useComponentProps('pagination', _props)

const { dir } = useLocale()
const appConfig = useAppConfig() as Pagination['AppConfig']

const rootProps = useForwardProps(reactivePick(props, 'as', 'defaultPage', 'disabled', 'itemsPerPage', 'page', 'showEdges', 'siblingCount', 'total'), emits)

// eslint-disable-next-line vue/no-dupe-keys
const firstIcon = computed(() => props.firstIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleRight : appConfig.ui.icons.chevronDoubleLeft))
const firstIcon = computed(() => props.firstIcon || appConfig.ui.icons.chevronDoubleLeft)
// eslint-disable-next-line vue/no-dupe-keys
const prevIcon = computed(() => props.prevIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronRight : appConfig.ui.icons.chevronLeft))
const prevIcon = computed(() => props.prevIcon || appConfig.ui.icons.chevronLeft)
// eslint-disable-next-line vue/no-dupe-keys
const nextIcon = computed(() => props.nextIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronLeft : appConfig.ui.icons.chevronRight))
const nextIcon = computed(() => props.nextIcon || appConfig.ui.icons.chevronRight)
// eslint-disable-next-line vue/no-dupe-keys
const lastIcon = computed(() => props.lastIcon || (dir.value === 'rtl' ? appConfig.ui.icons.chevronDoubleLeft : appConfig.ui.icons.chevronDoubleRight))
const lastIcon = computed(() => props.lastIcon || appConfig.ui.icons.chevronDoubleRight)

// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => tv({ extend: theme, ...(appConfig.ui?.pagination || {}) })())
Expand All @@ -152,12 +150,26 @@ const ui = computed(() => tv({ extend: theme, ...(appConfig.ui?.pagination || {}
<PaginationList v-slot="{ items }" data-slot="list" :class="ui.list({ class: props.ui?.list })">
<PaginationFirst v-if="props.showControls || !!slots.first" as-child data-slot="first" :class="ui.first({ class: props.ui?.first })">
<slot name="first">
<UButton :color="props.color" :variant="props.variant" :size="props.size" :icon="firstIcon" :to="props.to?.(1)" />
<UButton
:color="props.color"
:variant="props.variant"
:size="props.size"
:icon="firstIcon"
:ui="{ leadingIcon: 'rtl:-scale-x-100' }"
:to="props.to?.(1)"
/>
</slot>
</PaginationFirst>
<PaginationPrev v-if="props.showControls || !!slots.prev" as-child data-slot="prev" :class="ui.prev({ class: props.ui?.prev })">
<slot name="prev">
<UButton :color="props.color" :variant="props.variant" :size="props.size" :icon="prevIcon" :to="page > 1 ? props.to?.(page - 1) : undefined" />
<UButton
:color="props.color"
:variant="props.variant"
:size="props.size"
:icon="prevIcon"
:ui="{ leadingIcon: 'rtl:-scale-x-100' }"
:to="page > 1 ? props.to?.(page - 1) : undefined"
/>
</slot>
</PaginationPrev>

Expand Down Expand Up @@ -185,12 +197,26 @@ const ui = computed(() => tv({ extend: theme, ...(appConfig.ui?.pagination || {}

<PaginationNext v-if="props.showControls || !!slots.next" as-child data-slot="next" :class="ui.next({ class: props.ui?.next })">
<slot name="next">
<UButton :color="props.color" :variant="props.variant" :size="props.size" :icon="nextIcon" :to="page < pageCount ? props.to?.(page + 1) : undefined" />
<UButton
:color="props.color"
:variant="props.variant"
:size="props.size"
:icon="nextIcon"
:ui="{ leadingIcon: 'rtl:-scale-x-100' }"
:to="page < pageCount ? props.to?.(page + 1) : undefined"
/>
</slot>
</PaginationNext>
<PaginationLast v-if="props.showControls || !!slots.last" as-child data-slot="last" :class="ui.last({ class: props.ui?.last })">
<slot name="last">
<UButton :color="props.color" :variant="props.variant" :size="props.size" :icon="lastIcon" :to="props.to?.(pageCount)" />
<UButton
:color="props.color"
:variant="props.variant"
:size="props.size"
:icon="lastIcon"
:ui="{ leadingIcon: 'rtl:-scale-x-100' }"
:to="props.to?.(pageCount)"
/>
</slot>
</PaginationLast>
</PaginationList>
Expand Down
19 changes: 19 additions & 0 deletions test/components/Pagination.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,23 @@ describe('Pagination', () => {

expect(await axe(wrapper.element)).toHaveNoViolations()
})

it('mirrors default control icons in RTL', async () => {
const wrapper = await mountSuspended(Pagination, {
props: {
total: 100,
page: 5,
showEdges: true,
siblingCount: 1
}
})

for (const control of ['first', 'prev', 'next', 'last']) {
const icon = wrapper.find(`[data-slot="${control}"] [data-slot="leadingIcon"]`)
expect(icon.classes(), `${control} icon`).toContain('rtl:-scale-x-100')
}

const ellipsisIcon = wrapper.find('[data-slot="ellipsis"] [data-slot="leadingIcon"]')
expect(ellipsisIcon.classes(), 'ellipsis icon').not.toContain('rtl:-scale-x-100')
})
Comment on lines +59 to +76

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

Make this a real RTL regression test.

The test never mounts Pagination in an RTL context; it only checks the static rtl:-scale-x-100 class, which is applied regardless of direction. Add dir="rtl" to the mount context and cover explicit firstIcon, prevIcon, nextIcon, and lastIcon props, since custom icons are also part of the PR contract.

As per coding guidelines, component tests should cover relevant props, slots, and accessibility behavior.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/components/Pagination.spec.ts` around lines 59 - 76, Update the β€œmirrors
default control icons in RTL” test to mount Pagination with an RTL direction
context, then provide explicit firstIcon, prevIcon, nextIcon, and lastIcon props
and verify those custom controls receive the expected RTL mirroring behavior.
Retain the ellipsis assertion and add coverage for the relevant icon
rendering/accessibility contract without relying only on the static class.

Source: Coding guidelines

})
Loading
Loading