radix-vue / shadcn-vue

Vue port of shadcn-ui
https://www.shadcn-vue.com/
MIT License
3.51k stars 202 forks source link

[Feature]: Dot Buttons for Carousel #521

Open quankhuc opened 1 month ago

quankhuc commented 1 month ago

Adding Support for Dot Buttons in Carousel

The current Carousel does not support Dot Buttons. The library Embla Carousel supports the usage of dot buttons, as shown in this React example. Can we add support for dot buttons?

Updates: I think this setup for the carousel composable would work:

import { createInjectionState } from '@vueuse/core';
import emblaCarouselVue from 'embla-carousel-vue';
import { onMounted, ref } from 'vue';

const [useProvideCarousel, useInjectCarousel] = createInjectionState(({ opts, orientation, plugins }, emits) => {
  const [emblaNode, emblaApi] = emblaCarouselVue(
    {
      ...opts,
      axis: orientation === 'horizontal' ? 'x' : 'y',
    },
    plugins
  );

  function scrollPrev() {
    emblaApi.value?.scrollPrev();
  }
  function scrollNext() {
    emblaApi.value?.scrollNext();
  }
  function scrollTo(index) {
    emblaApi.value?.scrollTo(index)
  }

  const canScrollNext = ref(true);
  const canScrollPrev = ref(true);
  const selectedIndex = ref(0);
  const scrollSnaps = ref([])

  function onSelect(api) {
    canScrollNext.value = api.canScrollNext();
    canScrollPrev.value = api.canScrollPrev();
    selectedIndex.value = api.selectedScrollSnap();
    scrollSnaps.value = api.scrollSnapList();
  }

  onMounted(() => {
    if (!emblaApi.value) return;

    emblaApi.value?.on('init', onSelect);
    emblaApi.value?.on('reInit', onSelect);
    emblaApi.value?.on('select', onSelect);

    emits('init-api', emblaApi.value);
  });

  return {
    carouselRef: emblaNode,
    carouselApi: emblaApi,
    canScrollPrev,
    canScrollNext,
    scrollPrev,
    scrollNext,
    scrollTo,
    scrollSnaps,
    selectedIndex,
    orientation,
  };
});

function useCarousel() {
  const carouselState = useInjectCarousel();

  if (!carouselState) throw new Error('useCarousel must be used within a <Carousel />');

  return carouselState;
}

export { useCarousel, useProvideCarousel };

And here would be my version of the dot "buttons"

<template>
  <div
    v-for="(item, index) in scrollSnaps"
    :key="index"
    class="sm:mt-0 first:ml-0 border-1 w-2 h-2 mt-2 ml-2 border-gray-200 border-solid rounded-full"
    :class="[cn(props.class), index === selectedIndex ? 'border-transparent bg-blue-400' : 'bg-transparent']"
    @click="scrollTo(index)"
  />
</template>
<script setup>
import { useCarousel } from '~/composables/carousel';
import { cn } from '~/lib/utils';

const props = defineProps({
  class: {
    type: String,
    default: '',
  },
});

const { scrollTo, selectedIndex, scrollSnaps } = useCarousel();
</script>
marcosgomesneto commented 1 month ago

Very good, it worked for me! Waiting for implementation in the next version.

@quankhuc Are you going to make a pull request?

quankhuc commented 1 month ago

I can make it! Do you have any guidelines to make a PR? @marcosgomesneto

zernonia commented 1 month ago

Nice addition @quankhuc ! Do check out contribution section if you are unsure, and don't hesitate to create a PR for this 😁

quankhuc commented 1 month ago

Nice addition @quankhuc ! Do check out contribution section if you are unsure, and don't hesitate to create a PR for this 😁

I tried to open a PR but I don't have a permission to make PR to merge into radix-vue/shadcn-vue. Do you have a guideline for me to push a commit and open a PR? I am sorry this is my first time contributing into an open source project so I am not good at this

zernonia commented 1 month ago

No problem @quankhuc !

  1. Fork the branch
  2. Clone to your local
  3. Push changes to the forked repo
  4. Create a PR here