Skip to content

Commit

Permalink
Merge pull request #137 from BKWLD/rtl-support-via-changing-slide-order
Browse files Browse the repository at this point in the history
RTL support via changing slide order
  • Loading branch information
weotch authored Jan 20, 2024
2 parents 1b02eb4 + deaa201 commit cb3309c
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ For more examples, see the demo: https://vue-ssr-carousel.netlify.app.
| `no-drag` | `false` | Disables the ability to drag the carousel.
| `show-arrows` | `false` | Whether to show back/forward arrows. See https://vue-ssr-carousel.netlify.app/ui.
| `show-dots` | `false` | Whether to show dot style pagination dots. See https://vue-ssr-carousel.netlify.app/ui.
| `rtl` | `false` | Adjust layout for right to left sites. See https://vue-ssr-carousel.netlify.app/accessibility.
| `value` | `undefined` | Used as part of `v-model` to set the initial slide to show. See https://vue-ssr-carousel.netlify.app/events.
| `responsive` | `[]` | Adjust settings at breakpoints. See https://vue-ssr-carousel.netlify.app/responsive. Note, `loop` and `paginate-by-slide` cannot be set responsively.

Expand Down
11 changes: 11 additions & 0 deletions demo/components/demos/accessibility/rtl.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>

<div :style='{ direction: "rtl" }'>
<ssr-carousel rtl show-dots show-arrows>
<slide :index='1'></slide>
<slide :index='2'></slide>
<slide :index='3'></slide>
</ssr-carousel>
</div>

</template>
16 changes: 16 additions & 0 deletions demo/content/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,19 @@ By default, pages are referred to as "Page" in aria labels unless using `paginat
<slide>Story 3</slide>
</ssr-carousel>
```

## Support RTL

The `rtl` boolean props adjusts the layout and drag behavior for right-to-left sites (like when the `direction: rtl` CSS property has been set).

<demos-accessibility-rtl></demos-accessibility-rtl>

```vue
<div :style='{ direction: "rtl" }'>
<ssr-carousel rtl>
<slide :index='1'></slide>
<slide :index='2'></slide>
<slide :index='3'></slide>
</ssr-carousel>
</div>
```
4 changes: 4 additions & 0 deletions src/concerns/dimensions.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export default
# Check if the drag is currently out bounds
isOutOfBounds: -> @currentX > 0 or @currentX < @endX

# Helper for things that are triggered once dimensions are known so
# they can be more specific about their dependencies
dimensionsKnown: -> @carouselWidth and @viewportWidth

methods:

# Measure the component width for various calculations. Using
Expand Down
30 changes: 30 additions & 0 deletions src/concerns/rtl.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
###
Code related to supporting RTL layout
###
export default

# Add RTL prop
props: rtl: Boolean

# As an easy way to support rtl, update the index to the final value
# when RTL is enabled. This is change is combined with reversing the order
# of the slides in `ssr-carousel-track`. We're testing for the
# dimensionsKnown value as way to ensure that the final pages count is known
# since it depends on knowing the width of the carousel.
mounted: ->
return unless @rtl
if @dimensionsKnown
then @setInitialRtlIndex()
else unwatch = @$watch 'dimensionsKnown', =>
@setInitialRtlIndex()
unwatch()

methods:

# This should only be called once. Wait a tick so we're sure that the
# pages value has been calculated
setInitialRtlIndex: ->
setTimeout =>
@index = @pages - @value - 1
@jumpToIndex @index
, 0
58 changes: 35 additions & 23 deletions src/ssr-carousel-arrows.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@

.ssr-carousel-arrows

//- Back arrow
button.ssr-carousel-back-button(
:aria-label='`Previous ${pageLabel}`'
:aria-disabled='backDisabled'
//- Left arrow
button.ssr-carousel-left-button(
:aria-label='rtl ? nextLabel : backLabel'
:aria-disabled='leftDisabled'
:class='rtl ? "ssr-carousel-next-button" : "ssr-carousel-back-button"'
@click='$emit("back")')
slot(name='back' :disabled='backDisabled')
span.ssr-carousel-back-icon

//- Next arrow
button.ssr-carousel-next-button(
:aria-label='`Next ${pageLabel}`'
:aria-disabled='nextDisabled'
slot(
:name='rtl ? "next" : "back"'
:disabled='leftDisabled')
span.ssr-carousel-left-icon

//- Right arrow
button.ssr-carousel-right-button(
:aria-label='rtl ? backLabel : nextLabel'
:aria-disabled='rightDisabled'
:class='rtl ? "ssr-carousel-back-button" : "ssr-carousel-next-button"'
@click='$emit("next")')
slot(name='next' :disabled='nextDisabled')
span.ssr-carousel-next-icon
slot(
:name='rtl ? "back" : "next"'
:disabled='rightDisabled')
span.ssr-carousel-right-icon

</template>

Expand All @@ -32,12 +38,18 @@ export default
pages: Number
shouldLoop: Boolean
pageLabel: String
rtl: Boolean
computed:
# Make the labels
backLabel: -> "Previous #{@pageLabel}"
nextLabel: -> "Next #{@pageLabel}"
# Determine if button should be disabled because we're at the limits
backDisabled: -> @index == 0 unless @shouldLoop
nextDisabled: -> @index == @pages - 1 unless @shouldLoop
leftDisabled: -> @index == 0 unless @shouldLoop
rightDisabled: -> @index == @pages - 1 unless @shouldLoop
</script>

Expand All @@ -48,20 +60,20 @@ export default
@import './utils'
// Vertically center buttons
.ssr-carousel-back-button
.ssr-carousel-next-button
.ssr-carousel-left-button
.ssr-carousel-right-button
v-center()
resetButton()
// Align buttons near the edges
.ssr-carousel-back-button
.ssr-carousel-left-button
left 2%
.ssr-carousel-next-button
.ssr-carousel-right-button
right 2%
// Make a default icon
.ssr-carousel-back-icon
.ssr-carousel-next-icon
.ssr-carousel-left-icon
.ssr-carousel-right-icon
// Make a circle shape
display inline-block
Expand All @@ -88,12 +100,12 @@ export default
position relative
// Make triangle icons in the buttons
.ssr-carousel-back-icon
.ssr-carousel-left-icon
&:before
triangle 12px, 18px, white, 'left'
left -2px // Massage center
.ssr-carousel-next-icon
.ssr-carousel-right-icon
&:before
triangle 12px, 18px, white, 'right'
left 2px // Massage center
Expand Down
8 changes: 7 additions & 1 deletion src/ssr-carousel-dots.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.ssr-carousel-dots
button.ssr-carousel-dot-button(
v-for='i in pages' :key='i'
:aria-label='`Go to ${pageLabel} ${i}`'
:aria-label='makeLabel(i)'
:aria-disabled='isDisabled(i)'
@click='$emit("goto", i - 1)')

Expand All @@ -29,9 +29,15 @@ export default
boundedIndex: Number
pages: Number
pageLabel: String
rtl: Boolean
methods:
# Make the label for the dot
makeLabel: (index) ->
pageNumber = if @rtl then @pages - index + 1 else index
"Go to #{@pageLabel} #{pageNumber}"
# Check if dot index shuold be disabled
isDisabled: (index) -> @boundedIndex == index - 1
Expand Down
13 changes: 11 additions & 2 deletions src/ssr-carousel-track.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export default
activeSlides: Array
leftPeekingSlideIndex: Number
rightPeekingSlideIndex: Number
rtl: Boolean
dimensionsKnown: Number
data: ->
Expand Down Expand Up @@ -109,9 +111,17 @@ export default
# Get the list of non-text slides, including peeking clones. This doesn't
# work as a computed function
getSlideComponents: ->
[...(@$slots.default || []), ...(@$slots.clones || [])]
slides = [...(@$slots.default || []), ...(@$slots.clones || [])]
.filter (vnode) -> !vnode.text
# Reverses the slide if rtl and if the dimensions are known. This
# second condition exists to prevent the reversal from happening on SSR.
# Which is important because this logic is paired with setting the
# intial index to the last page which can't be known until the slide
# width is known.
if @rtl and @dimensionsKnown then slides = slides.reverse()
return slides
# Makes a clone of the vnode properties we'll be updating so the changes
# get rendered. Based on:
# https://github.com/vuejs/vue/issues/6052#issuecomment-313705168
Expand Down Expand Up @@ -161,7 +171,6 @@ export default
# Render the track and slotted slides
render: (create) ->
create @trackHTMLElement,
attrs: {role: "tablist" if @renderAsTablist}
class: [ 'ssr-carousel-track', { @dragging } ]
Expand Down
11 changes: 9 additions & 2 deletions src/ssr-carousel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
activeSlides,
leftPeekingSlideIndex,
rightPeekingSlideIndex,
rtl,
dimensionsKnown,
}`)

//- Render the slotted slides
Expand All @@ -52,7 +54,7 @@
//- Back / Next navigation
ssr-carousel-arrows(
v-if='showArrows'
v-bind='{ index, pages, shouldLoop, pageLabel }'
v-bind='{ index, pages, shouldLoop, pageLabel, rtl }'
@back='back'
@next='next')
template(#back='props'): slot(name='back-arrow' v-bind='props')
Expand All @@ -61,7 +63,7 @@
//- Dots navigation
ssr-carousel-dots(
v-if='showDots'
v-bind='{ boundedIndex, pages, pageLabel }'
v-bind='{ boundedIndex, pages, pageLabel, rtl }'
@goto='gotoDot')
template(#dot='props'): slot(name='dot' v-bind='props')

Expand Down Expand Up @@ -92,6 +94,7 @@ import looping from './concerns/looping'
import pagination from './concerns/pagination'
import peeking from './concerns/peeking'
import responsive from './concerns/responsive'
import rtl from './concerns/rtl'
import tweening from './concerns/tweening'
import variableWidth from './concerns/variable-width'
Expand All @@ -112,6 +115,7 @@ export default
pagination
responsive
peeking # After `responsive` so prop can access `gutter` prop
rtl
tweening
variableWidth
]
Expand Down Expand Up @@ -166,6 +170,9 @@ export default
.ssr-carousel
touch-action pan-y
// Internal logic expects ltr layout
direction ltr
// Posiition arrows relative to this
.ssr-carousel-slides
position relative
Expand Down

0 comments on commit cb3309c

Please sign in to comment.