diff --git a/apps/www/package.json b/apps/www/package.json index 4d799b54..34efd414 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -27,6 +27,7 @@ "clsx": "^2.0.0", "codesandbox": "^2.2.3", "date-fns": "^2.30.0", + "embla-carousel-vue": "8.0.0-rc17", "lucide-vue-next": "^0.276.0", "radix-vue": "^1.2.5", "tailwindcss-animate": "^1.0.7", diff --git a/apps/www/src/lib/registry/default/ui/carousel/Carousel.vue b/apps/www/src/lib/registry/default/ui/carousel/Carousel.vue new file mode 100644 index 00000000..56be5281 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/Carousel.vue @@ -0,0 +1,39 @@ + + + diff --git a/apps/www/src/lib/registry/default/ui/carousel/CarouselContent.vue b/apps/www/src/lib/registry/default/ui/carousel/CarouselContent.vue new file mode 100644 index 00000000..7d141430 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/CarouselContent.vue @@ -0,0 +1,21 @@ + + + diff --git a/apps/www/src/lib/registry/default/ui/carousel/CarouselItem.vue b/apps/www/src/lib/registry/default/ui/carousel/CarouselItem.vue new file mode 100644 index 00000000..5ff44c57 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/CarouselItem.vue @@ -0,0 +1,20 @@ + + + diff --git a/apps/www/src/lib/registry/default/ui/carousel/CarouselNext.vue b/apps/www/src/lib/registry/default/ui/carousel/CarouselNext.vue new file mode 100644 index 00000000..ec03059a --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/CarouselNext.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/www/src/lib/registry/default/ui/carousel/CarouselPrevious.vue b/apps/www/src/lib/registry/default/ui/carousel/CarouselPrevious.vue new file mode 100644 index 00000000..7a335f61 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/CarouselPrevious.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/www/src/lib/registry/default/ui/carousel/index.ts b/apps/www/src/lib/registry/default/ui/carousel/index.ts new file mode 100644 index 00000000..339a2e6e --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/index.ts @@ -0,0 +1,10 @@ +export { default as Carousel } from './Carousel.vue' +export { default as CarouselContent } from './CarouselContent.vue' +export { default as CarouselItem } from './CarouselItem.vue' +export { default as CarouselPrevious } from './CarouselPrevious.vue' +export { default as CarouselNext } from './CarouselNext.vue' +export { useCarousel } from './useCarousel' + +export { + type EmblaCarouselType as CarouselApi, +} from 'embla-carousel-vue' diff --git a/apps/www/src/lib/registry/default/ui/carousel/interface.ts b/apps/www/src/lib/registry/default/ui/carousel/interface.ts new file mode 100644 index 00000000..328d90a5 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/interface.ts @@ -0,0 +1,10 @@ +import { + type EmblaOptionsType as CarouselOptions, + type EmblaPluginType as CarouselPlugin, +} from 'embla-carousel-vue' + +export interface CarouselProps { + opts?: CarouselOptions + plugins?: CarouselPlugin[] + orientation?: 'horizontal' | 'vertical' +} diff --git a/apps/www/src/lib/registry/default/ui/carousel/useCarousel.ts b/apps/www/src/lib/registry/default/ui/carousel/useCarousel.ts new file mode 100644 index 00000000..84a2e1e9 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/carousel/useCarousel.ts @@ -0,0 +1,54 @@ +import { createInjectionState } from '@vueuse/core' +import emblaCarouselVue, { + type EmblaCarouselType as CarouselApi, +} from 'embla-carousel-vue' +import { onMounted, ref } from 'vue' +import type { CarouselProps } from './interface' + +const [useProvideCarousel, useInjectCarousel] = createInjectionState( + ({ + opts, orientation, plugins, + }: CarouselProps) => { + const [emblaNode, emblaApi] = emblaCarouselVue({ + ...opts, + axis: orientation === 'horizontal' ? 'x' : 'y', + }, plugins) + + function scrollPrev() { + emblaApi.value?.scrollPrev() + } + function scrollNext() { + emblaApi.value?.scrollNext() + } + + const canScrollNext = ref(true) + const canScrollPrev = ref(true) + + function onSelect(api: CarouselApi) { + canScrollNext.value = api.canScrollNext() + canScrollPrev.value = api.canScrollPrev() + } + + onMounted(() => { + if (!emblaApi.value) + return + + emblaApi.value?.on('init', onSelect) + emblaApi.value?.on('reInit', onSelect) + emblaApi.value?.on('select', onSelect) + }) + + return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation } + }, +) + +function useCarousel() { + const carouselState = useInjectCarousel() + + if (!carouselState) + throw new Error('useCarousel must be used within a ') + + return carouselState +} + +export { useCarousel, useProvideCarousel } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4c910f4..30745cec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: date-fns: specifier: ^2.30.0 version: 2.30.0 + embla-carousel-vue: + specifier: 8.0.0-rc17 + version: 8.0.0-rc17(vue@3.3.7) lucide-vue-next: specifier: ^0.276.0 version: 0.276.0(vue@3.3.7) @@ -6895,6 +6898,28 @@ packages: resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} dev: false + /embla-carousel-reactive-utils@8.0.0-rc17(embla-carousel@8.0.0-rc17): + resolution: {integrity: sha512-eluEOK/u5HdjYaTLC4bUG3iTCnyX7RsYix3il0aH4ZECOKa5fS+pVK2vrM17Mgw6C5Hyjcr3r3lfJtGerVzVsQ==} + peerDependencies: + embla-carousel: 8.0.0-rc17 + dependencies: + embla-carousel: 8.0.0-rc17 + dev: false + + /embla-carousel-vue@8.0.0-rc17(vue@3.3.7): + resolution: {integrity: sha512-+LHBImxj5Z8OhQHuBxbLhBNZ9cyS1UvXpCbPwlYvUllZ5XbzN08eFNiG7aBnOzxx2WzX2mc6tFzToN+HbO3QPg==} + peerDependencies: + vue: ^3.2.37 + dependencies: + embla-carousel: 8.0.0-rc17 + embla-carousel-reactive-utils: 8.0.0-rc17(embla-carousel@8.0.0-rc17) + vue: 3.3.7(typescript@5.2.2) + dev: false + + /embla-carousel@8.0.0-rc17: + resolution: {integrity: sha512-evF49b88VOitvqFtlvhvKVSu96Y8A+QSFdhok87Bfm8R7OYuk95FT+o8+M1GQLi/EhGDUlT193HTVAR0Wt2neQ==} + dev: false + /emoji-regex@10.2.1: resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} dev: false