diff --git a/src/components/Sliding/index.js b/src/components/Sliding/index.js
new file mode 100644
index 00000000..3a7677cf
--- /dev/null
+++ b/src/components/Sliding/index.js
@@ -0,0 +1,108 @@
+import { Component } from 'pet-dex-utilities';
+import { makeSwipable } from '../../utils/swiper';
+import './index.scss';
+
+const events = [
+ 'slide:add',
+ 'slide:next',
+ 'slide:previous',
+ 'slide:remove',
+ 'slides:clear',
+];
+
+const activeSlide = (slides, slide) => {
+ Array.from(slides).forEach((item) => {
+ item.classList.toggle('sliding__slide--active', item === slide);
+ });
+};
+
+const html = `
+
`;
+
+export default function Sliding({ slides = [] }) {
+ Component.call(this, { html, events });
+
+ this.slideIndex = 0;
+
+ slides.forEach((item) => this.add(item));
+
+ const $sliding = this.selected.get('sliding');
+
+ makeSwipable($sliding);
+
+ this.swipeLeft = () => {
+ this.next();
+ };
+
+ this.swipeRight = () => {
+ this.previous();
+ };
+
+ this.listen('mount', () => {
+ activeSlide(
+ Array.from(this.selected.get('sliding-content').children),
+ this.selected.get('sliding-content').children[0],
+ );
+ $sliding.addEventListener('swipe-left', this.swipeLeft());
+ $sliding.addEventListener('swipe-right', this.swipeRight());
+ });
+ this.listen('unmount', () => {
+ $sliding.removeEventListener('swipe-left', this.swipeLeft());
+ $sliding.removeEventListener('swipe-right', this.swipeRight());
+ });
+}
+
+Sliding.prototype = Object.assign(Sliding.prototype, Component.prototype, {
+ add(slide) {
+ slide.classList.add('sliding__slide');
+ this.selected.get('sliding-content').appendChild(slide);
+
+ this.emit('slide:add', slide);
+ },
+
+ remove(slide) {
+ this.selected.get('sliding-content').removeChild(slide);
+
+ this.emit('slide:remove', slide);
+ },
+
+ next() {
+ this.slideIndex += 1;
+ const slides = this.selected.get('sliding-content').children;
+
+ if (this.slideIndex > slides.length - 1) this.slideIndex = 0;
+
+ const slide = slides[this.slideIndex];
+ const container = this.selected.get('sliding').clientWidth;
+ this.selected.get('sliding-content').style.transform =
+ `translateX(${-this.slideIndex * container}px)`;
+ activeSlide(slides, slide);
+ this.emit('slide:next', slide);
+ },
+
+ previous() {
+ this.slideIndex -= 1;
+ const slides = this.selected.get('sliding-content').children;
+
+ if (this.slideIndex < 0) this.slideIndex = slides.length - 1;
+
+ const slide = slides[this.slideIndex];
+ const container = this.selected.get('sliding').clientWidth;
+
+ this.selected.get('sliding-content').style.transform =
+ `translateX(${-this.slideIndex * container}px)`;
+ activeSlide(slides, slide);
+ this.emit('slide:previous', slide);
+ },
+
+ clear() {
+ Array.from(this.selected.get('sliding-content').children).forEach((slide) =>
+ this.remove(slide),
+ );
+
+ this.emit('slides:clear');
+ },
+});
diff --git a/src/components/Sliding/index.scss b/src/components/Sliding/index.scss
new file mode 100644
index 00000000..5306fda2
--- /dev/null
+++ b/src/components/Sliding/index.scss
@@ -0,0 +1,20 @@
+.sliding {
+ width: 100%;
+ overflow: hidden;
+
+ margin: auto;
+
+ position: relative;
+
+ &__content {
+ display: flex;
+
+ transform: translateX(0);
+
+ transition: transform 0.6s ease-in-out;
+ }
+
+ &__slide {
+ flex: 0 0 100%;
+ }
+}
diff --git a/src/components/Sliding/index.spec.js b/src/components/Sliding/index.spec.js
new file mode 100644
index 00000000..6b3fab41
--- /dev/null
+++ b/src/components/Sliding/index.spec.js
@@ -0,0 +1,127 @@
+import { describe, expect, it } from 'vitest';
+import { render, screen } from '@testing-library/vanilla';
+import Slinding from '.';
+
+const $slide1 = document.createElement('div');
+$slide1.style.height = '200px';
+$slide1.style.backgroundColor = 'red';
+$slide1.textContent = 'slide 1';
+
+const $slide2 = document.createElement('div');
+$slide2.style.height = '200px';
+$slide2.style.backgroundColor = 'pink';
+$slide2.textContent = 'slide 2';
+
+const $slide3 = document.createElement('div');
+$slide3.style.height = '200px';
+$slide3.style.backgroundColor = 'green';
+$slide3.textContent = 'slide 3';
+
+const $slide4 = document.createElement('div');
+$slide4.style.height = '200px';
+$slide4.style.backgroundColor = 'black';
+$slide4.textContent = 'slide 4';
+
+const propsMock = {
+ slides: [$slide1, $slide2, $slide3],
+};
+
+const makeSut = (parameters) => render(new Slinding(parameters));
+
+describe('Slide', () => {
+ describe('on mount', () => {
+ it('renders with items passed by props', async () => {
+ makeSut(propsMock);
+ const slide1 = await screen.findByText('slide 1');
+ const slide2 = await screen.findByText('slide 2');
+ const slide3 = await screen.findByText('slide 3');
+
+ expect(slide1).toBeInTheDocument();
+ expect(slide2).toBeInTheDocument();
+ expect(slide3).toBeInTheDocument();
+ });
+ });
+
+ describe('on unmount', () => {
+ it('clear items', () => {
+ const element = makeSut(propsMock);
+
+ const callback = vi.fn();
+ element.listen('unmount', callback);
+
+ const slide1 = screen.queryByText('slide 1');
+ const slide2 = screen.queryByText('slide 2');
+ const slide3 = screen.queryByText('slide 3');
+
+ element.unmount();
+
+ expect(callback).toBeCalledWith();
+ expect(slide1).not.toBeInTheDocument();
+ expect(slide2).not.toBeInTheDocument();
+ expect(slide3).not.toBeInTheDocument();
+ });
+ });
+
+ it('add item programmatically', async () => {
+ const sliding = makeSut(propsMock);
+
+ const callback = vi.fn();
+ sliding.listen('slide:add', callback);
+ sliding.add($slide4);
+
+ const slide4 = await screen.findByText('slide 4');
+
+ expect(callback).toBeCalledWith($slide4);
+ expect(slide4).toBeInTheDocument();
+ });
+
+ it('remove item programmatically', () => {
+ const sliding = makeSut(propsMock);
+
+ const callback = vi.fn();
+ sliding.listen('slide:remove', callback);
+ sliding.remove($slide2);
+
+ const slide2 = screen.queryByText('slide 2');
+
+ expect(slide2).not.toBeInTheDocument();
+ expect(callback).toHaveBeenCalledWith($slide2);
+ });
+
+ it('next item programmatically', () => {
+ const sliding = makeSut(propsMock);
+
+ const callback = vi.fn();
+ sliding.listen('slide:next', callback);
+ sliding.next();
+
+ expect(callback).toBeCalledWith($slide2);
+ });
+
+ it('previous item programmatically', () => {
+ const sliding = makeSut(propsMock);
+
+ const callback = vi.fn();
+ sliding.listen('slide:previous', callback);
+ sliding.previous();
+
+ expect(callback).toBeCalledWith($slide3);
+ });
+
+ it('clear items programmatically', () => {
+ const sliding = makeSut(propsMock);
+
+ const callback = vi.fn();
+ sliding.listen('slides:clear', callback);
+ sliding.clear();
+
+ const slide1 = screen.queryByText('slide 1');
+ const slide2 = screen.queryByText('slide 2');
+ const slide3 = screen.queryByText('slide 3');
+
+ expect(callback).toBeCalledWith();
+ expect(slide1).not.toBeInTheDocument();
+ expect(slide2).not.toBeInTheDocument();
+ expect(slide3).not.toBeInTheDocument();
+ });
+});
diff --git a/src/stories/Sliding.stories.js b/src/stories/Sliding.stories.js
new file mode 100644
index 00000000..5cad90aa
--- /dev/null
+++ b/src/stories/Sliding.stories.js
@@ -0,0 +1,52 @@
+import { initializeSwiper } from '../utils/swiper';
+import Sliding from '../components/Sliding';
+import Button from '../components/Button';
+
+const $slide1 = document.createElement('div');
+$slide1.style.height = '200px';
+$slide1.style.backgroundColor = 'red';
+
+const $slide2 = document.createElement('div');
+$slide2.style.height = '200px';
+$slide2.style.backgroundColor = 'pink';
+
+const $slide3 = document.createElement('div');
+$slide3.style.height = '200px';
+$slide3.style.backgroundColor = 'green';
+
+const button4 = new Button({
+ text: '<',
+ isFullWidth: false,
+});
+
+const button5 = new Button({
+ text: '>',
+ isFullWidth: false,
+});
+
+export default {
+ title: 'Components/Sliding',
+ render: (args) => {
+ const sliding = new Sliding(args);
+ const $container = document.createElement('div');
+ initializeSwiper();
+ window.sliding = sliding;
+ sliding.mount($container);
+ button4.mount($container);
+ button5.mount($container);
+
+ button4.listen('click', () => sliding.previous());
+ button5.listen('click', () => sliding.next());
+
+ return $container;
+ },
+ argsTypes: {
+ slides: { control: 'object', defaultValue: [] },
+ },
+};
+
+export const Default = {
+ args: {
+ slides: [$slide1, $slide2, $slide3],
+ },
+};