Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTL support via changing slide order #137

Merged
merged 10 commits into from
Jan 20, 2024
Merged
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

Comment on lines +173 to +175
Copy link
Member Author

Choose a reason for hiding this comment

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

@azerozvn I realized one possible issue with this change. I'm setting the direction of the carousel wrapper to ltr so that my reversing of the order works as intended. However, this means that the slides will inherit this setting.

Like notice how the slide titles here are ltr:

image

I want to release a patch fix that sets the carousel track contents to direction: rtl when the rtl prop of the container is set. See any issue with that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright @azerozvn this change was released as v2.4.1 if you wanna upgrade to it. See #138.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you, and sorry I forgot to answer you here, I've updated sites-rtl to use 2.4.1

// Posiition arrows relative to this
.ssr-carousel-slides
position relative
Expand Down