diff --git a/README.md b/README.md index ac007b94..b48205a3 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ export default component$(() => { - Breadcrumb - Button - Card +- Carousel - Checkbox - Drawer - Dropdown diff --git a/apps/web/src/components/DocumentPage/DocumentPages.tsx b/apps/web/src/components/DocumentPage/DocumentPages.tsx index 72b43305..5063ff1c 100644 --- a/apps/web/src/components/DocumentPage/DocumentPages.tsx +++ b/apps/web/src/components/DocumentPage/DocumentPages.tsx @@ -1,11 +1,13 @@ -import { $, component$, Slot, useOnDocument, useSignal, useStore, useVisibleTask$ } from '@builder.io/qwik' -import './DocumentPage.css' +import { $, component$, Slot, useOnDocument, useSignal, useStore, useStyles$, useVisibleTask$ } from '@builder.io/qwik' +import styles from './DocumentPage.css?inline' import { toSlug } from '~/utils/slug' import { TableOfContents } from '../TableOfContents/TableOfContents' import { useDebounce } from 'flowbite-qwik' import { scrollTo } from '~/utils/scroll-to' export const DocumentPage = component$(() => { + useStyles$(styles) + const page = useSignal() const activeElement = useSignal() const sections = useStore>([]) diff --git a/apps/web/src/examples.ts b/apps/web/src/examples.ts index 44150704..69b06ed1 100644 --- a/apps/web/src/examples.ts +++ b/apps/web/src/examples.ts @@ -555,6 +555,82 @@ export const examples: Record = { height: '600', }, ], + carousel: [ + { + title: 'Default carousel', + description: 'Use this example by adding a series of images inside the component.', + url: '/examples/[theme-rtl]/carousel/01-default-carousel', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n
\n )\n})', + height: '300', + }, + { + title: 'Slider content', + description: 'Instead of images you can also use any type of markup and content inside the carousel such as simple text.', + url: '/examples/[theme-rtl]/carousel/02-slider-content', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n
Slide 1
\n
\n \n
Slide 2
\n
\n \n
Slide 3
\n
\n \n
Slide 4
\n
\n \n
Slide 5
\n
\n
\n
\n )\n})', + height: '300', + }, + { + title: 'Static carousel', + description: + 'Pass the slideAuto prop to false to the carousel component to make it static and disable the automatic sliding functionality. This does not disable the control or indicator buttons.', + url: '/examples/[theme-rtl]/carousel/03-static-carousel', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n
\n )\n})', + height: '300', + }, + { + title: 'Sliding interval', + description: 'Use the slideInterval prop to set the interval between slides in milliseconds. The default value is 3000.', + url: '/examples/[theme-rtl]/carousel/04-sliding-interval', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n
\n )\n})', + height: '300', + }, + { + title: 'Carousel without controls', + description: 'Use the noControls prop to hide the controls of the carousel', + url: '/examples/[theme-rtl]/carousel/05-carousel-without-controls', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n
\n )\n})', + height: '300', + }, + { + title: 'Carousel without indicators', + description: 'Use the noIndicators prop to hide the indicators of the carousel', + url: '/examples/[theme-rtl]/carousel/06-carousel-without-indicators', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n
\n )\n})', + height: '300', + }, + { + title: 'Slider complex content', + description: 'Instead of images or simple texte, we can use any component we need', + url: '/examples/[theme-rtl]/carousel/07-slider-complex-content', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Card, Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n
\n \n
Noteworthy technology acquisitions 2021
\n

\n Here are the biggest enterprise technology acquisitions of 2021 so far, in reverse chronological order.\n

\n
\n
\n
\n \n
\n \n
Crystal Clear Oasis
\n

\n Here are the biggest enterprise technology acquisitions of 2021 so far, in reverse chronological order.\n

\n
\n
\n
\n \n
\n \n
Lost in the Sands
\n

\n Here are the biggest enterprise technology acquisitions of 2021 so far, in reverse chronological order.\n

\n
\n
\n
\n
\n
\n )\n})', + height: '600', + }, + { + title: 'Scrollable carousel', + description: 'Use this example using the prop "scrollable" to scroll inside the carousel', + url: '/examples/[theme-rtl]/carousel/08-carousel-scrollable', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n {\n console.log(\'changed !\')\n }}\n >\n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n
\n )\n})', + height: '300', + }, + { + title: 'Pause On Hover', + description: + 'To conditionally pause the carousel on mouse hover (desktop), or touch and hold (mobile), you can use the pauseOnHover property on the component. Default value is false.', + url: '/examples/[theme-rtl]/carousel/09-carousel-pause-on-hover', + content: + 'import { component$ } from \'@builder.io/qwik\'\nimport { Carousel, CarouselSlice } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n
\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n ...\n \n \n
\n )\n})', + height: '300', + }, + ], checkbox: [ { title: 'Checkbox example', diff --git a/apps/web/src/routes/docs/components/carousel/index.tsx b/apps/web/src/routes/docs/components/carousel/index.tsx new file mode 100644 index 00000000..83f064d6 --- /dev/null +++ b/apps/web/src/routes/docs/components/carousel/index.tsx @@ -0,0 +1,19 @@ +import { component$ } from '@builder.io/qwik' +import { ComponentDocPage } from '~/components/ComponentDocPage/ComponentDocPage' +import { DocumentHead } from '@builder.io/qwik-city' + +export default component$(() => { + return ( + +
+ Use the responsive carousel component to allow users to slide through multiple items and navigate between them using the control buttons and + indicators. Choose from multiple examples and options to update the intervals, make the carousel static and set custom control button and + indicator by configuring React and the utility classes from Tailwind CSS. +
+
+ ) +}) + +export const head: DocumentHead = () => ({ + title: 'Qwik Carousel - Flowbite', +}) diff --git a/apps/web/src/routes/docs/layout.tsx b/apps/web/src/routes/docs/layout.tsx index c6f2db11..4bc6ae66 100644 --- a/apps/web/src/routes/docs/layout.tsx +++ b/apps/web/src/routes/docs/layout.tsx @@ -35,7 +35,7 @@ export default component$(() => { Quickstart - + Accordion @@ -60,6 +60,9 @@ export default component$(() => { Card + + Carousel + Drawer diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/01-default-carousel/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/01-default-carousel/index@examples.tsx new file mode 100644 index 00000000..a2769674 --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/01-default-carousel/index@examples.tsx @@ -0,0 +1,38 @@ +/** + * title: Default carousel + * description: Use this example by adding a series of images inside the component. + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + + ... + + + ... + + + ... + + + ... + + + ... + + +
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/02-slider-content/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/02-slider-content/index@examples.tsx new file mode 100644 index 00000000..3db8a4d6 --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/02-slider-content/index@examples.tsx @@ -0,0 +1,38 @@ +/** + * title: Slider content + * description: Instead of images you can also use any type of markup and content inside the carousel such as simple text. + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + +
Slide 1
+
+ +
Slide 2
+
+ +
Slide 3
+
+ +
Slide 4
+
+ +
Slide 5
+
+
+
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/03-static-carousel/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/03-static-carousel/index@examples.tsx new file mode 100644 index 00000000..f01e1481 --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/03-static-carousel/index@examples.tsx @@ -0,0 +1,38 @@ +/** + * title: Static carousel + * description: Pass the slideAuto prop to false to the carousel component to make it static and disable the automatic sliding functionality. This does not disable the control or indicator buttons. + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + + ... + + + ... + + + ... + + + ... + + + ... + + +
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/04-sliding-interval/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/04-sliding-interval/index@examples.tsx new file mode 100644 index 00000000..82b1ee41 --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/04-sliding-interval/index@examples.tsx @@ -0,0 +1,38 @@ +/** + * title: Sliding interval + * description: Use the slideInterval prop to set the interval between slides in milliseconds. The default value is 3000. + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + + ... + + + ... + + + ... + + + ... + + + ... + + +
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/05-carousel-without-controls/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/05-carousel-without-controls/index@examples.tsx new file mode 100644 index 00000000..614ec74c --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/05-carousel-without-controls/index@examples.tsx @@ -0,0 +1,38 @@ +/** + * title: Carousel without controls + * description: Use the noControls prop to hide the controls of the carousel + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + + ... + + + ... + + + ... + + + ... + + + ... + + +
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/06-carousel-without-indicators/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/06-carousel-without-indicators/index@examples.tsx new file mode 100644 index 00000000..a23d7f4e --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/06-carousel-without-indicators/index@examples.tsx @@ -0,0 +1,38 @@ +/** + * title: Carousel without indicators + * description: Use the noIndicators prop to hide the indicators of the carousel + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + + ... + + + ... + + + ... + + + ... + + + ... + + +
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/07-slider-complex-content/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/07-slider-complex-content/index@examples.tsx new file mode 100644 index 00000000..6688a6e3 --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/07-slider-complex-content/index@examples.tsx @@ -0,0 +1,53 @@ +/** + * title: Slider complex content + * description: Instead of images or simple texte, we can use any component we need + * height: 600 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Card, Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + +
+ +
Noteworthy technology acquisitions 2021
+

+ Here are the biggest enterprise technology acquisitions of 2021 so far, in reverse chronological order. +

+
+
+
+ +
+ +
Crystal Clear Oasis
+

+ Here are the biggest enterprise technology acquisitions of 2021 so far, in reverse chronological order. +

+
+
+
+ +
+ +
Lost in the Sands
+

+ Here are the biggest enterprise technology acquisitions of 2021 so far, in reverse chronological order. +

+
+
+
+
+
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/08-carousel-scrollable/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/08-carousel-scrollable/index@examples.tsx new file mode 100644 index 00000000..a8d313a7 --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/08-carousel-scrollable/index@examples.tsx @@ -0,0 +1,44 @@ +/** + * title: Scrollable carousel + * description: Use this example using the prop "scrollable" to scroll inside the carousel + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ { + console.log('changed !') + }} + > + + ... + + + ... + + + ... + + + ... + + + ... + + +
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/apps/web/src/routes/examples/[theme-rtl]/carousel/09-carousel-pause-on-hover/index@examples.tsx b/apps/web/src/routes/examples/[theme-rtl]/carousel/09-carousel-pause-on-hover/index@examples.tsx new file mode 100644 index 00000000..0f25c947 --- /dev/null +++ b/apps/web/src/routes/examples/[theme-rtl]/carousel/09-carousel-pause-on-hover/index@examples.tsx @@ -0,0 +1,38 @@ +/** + * title: Pause On Hover + * description: To conditionally pause the carousel on mouse hover (desktop), or touch and hold (mobile), you can use the pauseOnHover property on the component. Default value is false. + * height: 300 + */ + +import { component$ } from '@builder.io/qwik' +import { StaticGenerateHandler } from '@builder.io/qwik-city' +import { Carousel, CarouselSlice } from 'flowbite-qwik' +import { staticGenerateHandler } from '~/routes/examples/[theme-rtl]/layout' + +export default component$(() => { + return ( +
+ + + ... + + + ... + + + ... + + + ... + + + ... + + +
+ ) +}) + +export const onStaticGenerate: StaticGenerateHandler = async () => { + return staticGenerateHandler() +} diff --git a/packages/lib/README.md b/packages/lib/README.md index ac007b94..b48205a3 100644 --- a/packages/lib/README.md +++ b/packages/lib/README.md @@ -129,6 +129,7 @@ export default component$(() => { - Breadcrumb - Button - Card +- Carousel - Checkbox - Drawer - Dropdown diff --git a/packages/lib/package.json b/packages/lib/package.json index 6b37a98c..b96ccbfe 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -1,6 +1,6 @@ { "name": "flowbite-qwik", - "version": "0.19.0", + "version": "0.20.0", "description": "Unofficial Qwik components built for Flowbite and Tailwind CSS", "keywords": [ "design-system", diff --git a/packages/lib/src/components/Carousel/Carousel.css b/packages/lib/src/components/Carousel/Carousel.css new file mode 100644 index 00000000..88894a56 --- /dev/null +++ b/packages/lib/src/components/Carousel/Carousel.css @@ -0,0 +1,27 @@ +/* hidden */ +.carousel-scrollable { + scrollbar-width: none; + &::-webkit-scrollbar { + @apply hidden; + } +} + +/* width */ +.carousel-scrollable::-webkit-scrollbar { + @apply h-1 w-1; +} + +/* Track */ +.carousel-scrollable::-webkit-scrollbar-track { + @apply bg-gray-400; +} + +/* Handle */ +.carousel-scrollable::-webkit-scrollbar-thumb { + @apply rounded-s bg-gray-600; +} + +/* Handle on hover */ +.carousel-scrollable::-webkit-scrollbar-thumb:hover { + @apply bg-gray-800; +} diff --git a/packages/lib/src/components/Carousel/Carousel.tsx b/packages/lib/src/components/Carousel/Carousel.tsx new file mode 100644 index 00000000..17134d15 --- /dev/null +++ b/packages/lib/src/components/Carousel/Carousel.tsx @@ -0,0 +1,273 @@ +import { + $, + component$, + createElement, + FunctionComponent, + JSXChildren, + JSXNode, + PropsOf, + Slot, + useSignal, + useStore, + useStyles$, + useVisibleTask$, +} from '@builder.io/qwik' +import { getChild } from '~/utils/getChild' +import uuid from '~/utils/uuid' +import { IconAngleLeftSolid, IconAngleRightOutline } from 'flowbite-qwik-icons' +import styles from './Carousel.css?inline' + +interface ComponentType { + id: number + children: JSXChildren +} + +type CarouselProps = PropsOf<'div'> & { + noIndicators?: boolean + noControls?: boolean + slideAuto?: boolean + slideInterval?: number + scrollable?: boolean + pauseOnHover?: boolean + onSlideChanged$?: () => void +} +export const Carousel: FunctionComponent = ({ children, ...props }) => { + const components: ComponentType[] = [] + const classesToAdd = 'absolute top-1/2 left-1/2 w-full -translate-x-1/2 -translate-y-1/2' + + getChild(children, [ + { + component: CarouselSlice, + foundComponentCallback: (child, index) => { + const childrenIsArray = Array.isArray(child.children) + + let computedChildren + if (childrenIsArray) { + computedChildren = createElement('div', { key: uuid(), class: classesToAdd }, child.children) + } else { + const cc = child.children as JSXNode + + computedChildren = createElement( + cc.type as string, + { + class: cc.immutableProps?.['class'] + ' ' + classesToAdd, + alt: cc.immutableProps?.['alt'], + src: cc.immutableProps?.['src'], + key: cc.key, + }, + cc.children, + ) + } + + components.push({ + id: index, + children: computedChildren, + }) + }, + }, + ]) + + return +} + +type CarouselSliceProps = PropsOf<'div'> + +export const CarouselSlice = component$(() => { + return +}) + +/** + * Implementation + */ + +type InnerCarouselProps = CarouselProps & { + components: ComponentType[] +} + +type CarouselStore = { + currentPicture: number + direction: string + interval: ReturnType + automaticSlide: (this: CarouselStore) => void + nextPicture: (this: CarouselStore) => void + resetInterval: (this: CarouselStore) => void + slideTo: (this: CarouselStore, index: number) => void + previousPicture: (this: CarouselStore) => void + goTo: (this: CarouselStore) => void + mouseEnter: (this: CarouselStore) => void + mouseLeave: (this: CarouselStore) => void +} + +const InnerCarousel = component$( + ({ + components, + noIndicators = false, + noControls = false, + onSlideChanged$, + pauseOnHover = false, + scrollable = false, + slideAuto = true, + slideInterval = 3000, + }) => { + useStyles$(styles) + + const carouselContainer = useSignal() + + const state = useStore({ + currentPicture: 0, + direction: '', + interval: undefined, + + automaticSlide: $(function (this: CarouselStore) { + if (!slideAuto) return + // eslint-disable-next-line @typescript-eslint/no-this-alias + const that = this + this.interval = setInterval(function () { + that.nextPicture() + }, slideInterval) + }), + + resetInterval: $(function (this: CarouselStore) { + clearInterval(this.interval) + this.automaticSlide() + }), + + slideTo: $(function (this: CarouselStore, index: number) { + this.currentPicture = index + this.goTo() + this.resetInterval() + }), + + nextPicture: $(function (this: CarouselStore) { + if (this.currentPicture !== components.length - 1) { + this.currentPicture += 1 + } else { + this.currentPicture = 0 + } + + this.goTo() + this.direction = 'right' + this.resetInterval() + }), + + previousPicture: $(function (this: CarouselStore) { + if (this.currentPicture !== 0) { + this.currentPicture -= 1 + } else { + this.currentPicture = components.length - 1 + } + + this.goTo() + this.direction = 'left' + this.resetInterval() + }), + + goTo: $(function (this: CarouselStore) { + onSlideChanged$?.() + if (carouselContainer.value) { + carouselContainer.value.scrollLeft = carouselContainer.value.clientWidth * this.currentPicture + } + }), + + mouseEnter: $(function (this: CarouselStore) { + if (!pauseOnHover) return + clearInterval(this.interval) + }), + + mouseLeave: $(function (this: CarouselStore) { + if (!pauseOnHover) return + this.resetInterval() + }), + }) + + const onScroll = $(() => { + if (!scrollable) return + if (carouselContainer.value) { + state.currentPicture = Math.round(carouselContainer.value.scrollLeft / carouselContainer.value.clientWidth) + } + }) + + useVisibleTask$(() => { + if (slideAuto) { + state.automaticSlide() + } + }) + + return ( +
+
{ + state.mouseEnter() + }} + onMouseLeave$={() => { + state.mouseLeave() + }} + class={[ + 'snap-x flex h-full snap-mandatory overflow-y-hidden overflow-x-scroll scroll-smooth rounded-lg', + { + 'overflow-hidden !overflow-x-hidden [scrollbar-width:none]': !scrollable, + '[&::-webkit-scrollbar]:[-webkit-appearance:none !important] [&::-webkit-scrollbar]:!hidden [&::-webkit-scrollbar]:!h-0 [&::-webkit-scrollbar]:!w-0 [&::-webkit-scrollbar]:!bg-transparent': + !scrollable, + 'carousel-scrollable': scrollable, + }, + ]} + > + {components.map((comp, i) => ( +
+ <>{comp.children} +
+ ))} +
+ {!noIndicators && ( +
+ {components.map((comp, i) => ( +
+ )} + {!noControls && !scrollable && ( + <> + + + + )} +
+ ) + }, +) diff --git a/packages/lib/src/components/Carousel/index.ts b/packages/lib/src/components/Carousel/index.ts new file mode 100644 index 00000000..254a4ec6 --- /dev/null +++ b/packages/lib/src/components/Carousel/index.ts @@ -0,0 +1 @@ +export { Carousel, CarouselSlice } from './Carousel' diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index 04a36fc5..bab83ed3 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -7,6 +7,7 @@ export * from './components/Banner' export * from './components/Breadcrumb' export * from './components/Button' export * from './components/Card' +export * from './components/Carousel' export * from './components/Checkbox' export * from './components/Drawer' export * from './components/Dropdown'