diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts index 535181da43e0f56cd359555ca45d71ef7430a299..829d2a39644e817923fcbea179f2bd65cf1658c1 100644 --- a/src/common/interfaces/componentsProps.ts +++ b/src/common/interfaces/componentsProps.ts @@ -79,6 +79,16 @@ export interface IPaginatorProps { darknessTheme?: TDarkness; } +export interface ICarouselProps { + itemsProps: unknown[]; + width?: string; + size?: TSize; + perView?: number; + perScroll?: number; + theme?: TThemeColor; + darknessTheme?: TDarkness; +} + export interface IMDProps { items: IMDItemProps[]; size?: TSize; diff --git a/src/components/Carousel/Carousel.stories.ts b/src/components/Carousel/Carousel.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..a69e2a2f33a977fc673a1bdf1f8acc0b14072b8a --- /dev/null +++ b/src/components/Carousel/Carousel.stories.ts @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; + +import Carousel from './Carousel.vue'; + +const meta: Meta = { + title: 'Components/Carousel', + component: Carousel, + tags: ['pick'], + parameters: { + docs: { + description: { + component: 'A component to define number inputs with a dial.', + }, + }, + }, + argTypes: { + buttons: { control: 'boolean' }, + showLabel: { control: 'boolean' }, + colorAsTheme: { control: 'boolean' }, + textBold: { control: 'boolean' }, + min: { control: 'number' }, + max: { control: 'number' }, + step: { control: 'number' }, + fontSize: { control: 'text' }, + textBefore: { control: 'text' }, + textAfter: { control: 'text' }, + colorGaps: { control: 'object' }, + size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] }, + background: { control: 'color' }, + darknessTheme: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] }, + darknessNegativeTheme: { + control: 'select', + options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'], + }, + darknessColor: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] }, + theme: { + control: 'select', + options: [ + 'white', + 'blue', + 'sky', + 'cyan', + 'teal', + 'green', + 'yellow', + 'orange', + 'pink', + 'fuchsia', + 'purple', + 'indigo', + 'rose', + 'red', + 'black', + ], + }, + negativeTheme: { + control: 'select', + options: [ + 'white', + 'blue', + 'sky', + 'cyan', + 'teal', + 'green', + 'yellow', + 'orange', + 'pink', + 'fuchsia', + 'purple', + 'indigo', + 'rose', + 'red', + 'black', + ], + }, + color: { + control: 'select', + options: [ + 'white', + 'blue', + 'sky', + 'cyan', + 'teal', + 'green', + 'yellow', + 'orange', + 'pink', + 'fuchsia', + 'purple', + 'indigo', + 'rose', + 'red', + 'black', + ], + }, + }, +} satisfies Meta<typeof Carousel>; + +export default meta; + +type Story = StoryObj<typeof meta>; + +export const Simple: Story = { + args: {}, +}; diff --git a/src/components/Carousel/Carousel.vue b/src/components/Carousel/Carousel.vue new file mode 100644 index 0000000000000000000000000000000000000000..e9310a79f4e9e90a95f073227aee6a6741cee946 --- /dev/null +++ b/src/components/Carousel/Carousel.vue @@ -0,0 +1,46 @@ +<script setup lang="ts"> +import type { ICarouselProps } from '@interfaces/componentsProps'; +import CarouselArrowContainer from '@components/Carousel/CarouselArrowContainer.vue'; +import { computed, ref } from 'vue'; +import { convertThemeToColor, convertThemeToTextColor } from '@helpers/common'; +import ArrowLeftShortIcon from '@icons/Mono/ArrowLeftShortIcon.vue'; +import ArrowRightShortIcon from '@icons/Mono/ArrowRightShortIcon.vue'; + +const props = withDefaults(defineProps<ICarouselProps>(), { + itemsProps: () => [], + size: 'normal', +}); + +const current = ref(1); + +const itemsLength = computed(() => Math.ceil(props.itemsProps.length / props.perView)); +const color = computed(() => convertThemeToColor(props.theme, props.darknessTheme)); +const textColor = computed(() => convertThemeToTextColor(props.theme, props.darknessTheme)); +const isStartDisabled = computed(() => current.value === 1); +const isEndDisabled = computed(() => current.value === itemsLength.value); +const iconSize = computed(() => { + const size = props.size; + if (size === 'normal') return '10'; + if (size === 'large') return '15'; + if (size === 'huge') return '18'; + return '7'; +}); +</script> + +<template> + <section :style="`width: ${width}; min-height: 100px`" class="carouselContainer"> + <CarouselArrowContainer :textColor="textColor" :color="color" :disable="isStartDisabled"> + <ArrowLeftShortIcon :color="isStartDisabled ? '#aaa' : textColor" :size="iconSize" /> + </CarouselArrowContainer> + <slot /> + <CarouselArrowContainer :textColor="textColor" :color="color" :disable="isEndDisabled"> + <ArrowRightShortIcon :color="isEndDisabled ? '#aaa' : textColor" :size="iconSize" /> + </CarouselArrowContainer> + </section> +</template> + +<style scoped> +.carouselContainer { + display: flex; +} +</style> diff --git a/src/components/Carousel/CarouselArrowContainer.vue b/src/components/Carousel/CarouselArrowContainer.vue new file mode 100644 index 0000000000000000000000000000000000000000..7dbf50edbc763353fa6fb00cb7de5f8d4bc3a00e --- /dev/null +++ b/src/components/Carousel/CarouselArrowContainer.vue @@ -0,0 +1,86 @@ +<script setup lang="ts"> +defineProps<{ + disable?: boolean; + textColor: string; + color: string; +}>(); +</script> + +<template> + <div class="arrowContainer"> + <div + :class="[ + 'icon', + { + disable, + }, + ]" + > + <div + :class="[ + 'bg', + { + disableBg: disable, + }, + ]" + ></div> + <slot /> + </div> + </div> +</template> + +<style scoped> +.arrowContainer { + width: 50px; + min-height: 100%; + display: flex; + justify-content: center; + align-items: center; +} +.icon { + position: relative; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + line-height: 1.2; + color: v-bind(textColor); +} +.icon::before { + content: ''; + position: absolute; + width: 100%; + height: 100%; + border-radius: 50%; + z-index: -1; + background-color: v-bind(color); +} +.icon:hover > .bg { + background-color: v-bind(textColor); + opacity: 0.1; +} +.icon:active > .bg { + opacity: 0.2; +} +.bg { + width: 100%; + height: 100%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: 10px; + z-index: 5; + border-radius: 50%; + background-color: transparent; + opacity: 0; + transition: all 0.2s ease; +} +.disable { + cursor: auto; + pointer-events: none; +} +.disableBg { + background-color: white !important; +} +</style>