add changes

This commit is contained in:
2024-07-09 11:00:50 +02:00
parent 6c19db0c07
commit b61c96f9e5
37 changed files with 90 additions and 117 deletions

View File

@ -0,0 +1,53 @@
<template>
<div :class="classes">
<slot />
</div>
</template>
<script lang="ts" setup>
import type { AccordionProps, tAccordionStates } from './types';
import { twMerge } from 'tailwind-merge';
const props = withDefaults(defineProps<AccordionProps>(), {
alwaysOpen: true,
openFirstItem: true,
flush: false,
mode: 'single'
});
const classes = computed(() => twMerge('', props.class));
const state = ref({
options: { ...props },
panels: {},
initialized: false
} as tAccordionStates);
const openCloseEvent = (panelId: number): void => {
let isanyopened = false;
if (props.mode == 'single') {
for (const i in state.value.panels) {
if (parseInt(i) == panelId) {
state.value.panels[i].isOpen = !state.value.panels[i].isOpen;
} else {
state.value.panels[i].isOpen = false;
}
}
} else {
state.value.panels[panelId].isOpen = !state.value.panels[panelId].isOpen;
}
if (props.alwaysOpen) {
for (const i in state.value.panels) {
if (state.value.panels[i].isOpen) {
isanyopened = true;
}
}
if (!isanyopened) {
state.value.panels[panelId].isOpen = true;
}
}
};
provide('accordionState', state);
provide('openCloseEvent', openCloseEvent);
</script>

View File

@ -0,0 +1,30 @@
<template>
<div v-show="isOpen" :class="contentClasses">
<!-- <MaalTransitionFade :delay="0" :duration="500"> -->
<slot />
<!-- </MaalTransitionFade> -->
</div>
</template>
<script lang="ts" setup>
import type { tAccordionStates } from './types';
const panelId = inject('panelId') as Ref<number>;
if (panelId == undefined) {
throw {
message: 'Looks that you are trying to use component AccordionContent without AccordionPanel as parent',
statusCode: 507
};
}
const accordionState = inject('accordionState') as Ref<tAccordionStates>;
if (accordionState == undefined) {
throw {
message: 'Looks that you are trying to use component AccordionContent without AccordionPanel as parent',
statusCode: 507
};
}
const contentClasses = 'p-6';
const isOpen = computed(() => accordionState.value.panels[panelId.value].isOpen);
</script>

View File

@ -0,0 +1,50 @@
<template>
<div :class="wrapperClasses">
<button
type="button"
:class="[headerClasses]"
@click="
useRipple($event, props.ripple);
openCloseEvent(panelId);
"
>
<span class="w-full">
<slot />
</span>
</button>
</div>
</template>
<script lang="ts" setup>
import type { tAccordionStates, AccordionHeaderProps } from './types';
const panelId = inject('panelId') as Ref<number>;
if (panelId == undefined) {
throw {
message: 'Looks that you are trying to use component AccordionHeader without AccordionPanel as parent',
statusCode: 507
};
}
const accordionState = inject('accordionState') as Ref<tAccordionStates>;
if (accordionState == undefined) {
throw {
message: 'Looks that you are trying to use component AccordionHeader without AccordionPanel as parent',
statusCode: 507
};
}
const openCloseEvent = inject('openCloseEvent') as Function;
if (openCloseEvent == undefined) {
throw {
message: 'Looks that you are trying to use component AccordionHeader without AccordionPanel or Accordion as parent',
statusCode: 507
};
}
const headerClasses = `flex items-center justify-between w-full px-6 py-4 font-medium rtl:text-right gap-3 text-left`;
const wrapperClasses = '';
const props = withDefaults(defineProps<AccordionHeaderProps>(), {
ripple: true
});
</script>

View File

@ -0,0 +1,32 @@
<template>
<div class="md:flex last:border-b-0">
<slot name="header" />
<slot name="content" />
</div>
</template>
<script lang="ts" setup>
import type { AccordionPanelProps, tAccordionStates } from './types';
withDefaults(defineProps<AccordionPanelProps>(), {});
const accordionState = inject('accordionState') as Ref<tAccordionStates>;
if (accordionState == undefined) {
throw {
message: 'Looks that you are trying to use component AccordionPanel without Accordion as parent',
statusCode: 507
};
}
const panelId = ref(Object.keys(accordionState.value.panels).length);
// inject state of this panel
accordionState.value.panels[panelId.value] = {
isOpen: ((): boolean => {
if (accordionState.value.options.openFirstItem && panelId.value == 0) {
return true;
}
return false;
})()
};
provide('panelId', panelId);
</script>

View File

@ -0,0 +1,41 @@
export type tAccordionMode = 'flush' | 'alwaysOpen' | 'default';
export type tAccordionPanel = {
isOpen: boolean;
};
type tAccordionPanels = {
[key: string]: tAccordionPanel;
};
type tStateElement = {
id: string;
flush: boolean;
alwaysOpen: boolean;
openFirstItem: boolean;
panels: tAccordionPanels;
};
export type tState = {
[key: string]: tStateElement;
};
//////////////
export interface AccordionHeaderProps {
ripple?: boolean;
}
export interface AccordionProps {
openFirstItem?: boolean;
alwaysOpen?: boolean;
mode?: 'single' | 'multi';
class?: string | string[];
}
export type tAccordionStates = {
options: AccordionProps;
initialized: boolean;
panels: { [key: number]: tAccordionPanel };
};
export type AccordionPanelProps = {};

View File

@ -0,0 +1,45 @@
<template>
<button :class="rootClasses" @click="useRipple($event, true)">
<div :class="outerClasses">
<div :class="innerClasses">
<slot></slot>
</div>
</div>
</button>
</template>
<script setup lang="ts">
// import { useRipple } from "#imports";
import { computed, ref } from "vue";
import type { ButtonProps, PTAttribs } from "./types";
import { twMerge } from "tailwind-merge";
const props = withDefaults(defineProps<ButtonProps>(), {
rounded: true,
spinerSize: "6",
spinerColor: "accent1",
spinerPlace: "prefix",
});
const classes = ref({
root: {
class: ``,
},
outer: {
class: ``,
},
inner: {
class: ``,
},
} as PTAttribs);
const rootClasses = computed(() =>
twMerge(classes.value.root?.class, props.pt?.root?.class)
);
const innerClasses = computed(() =>
twMerge(classes.value.inner?.class, props.pt?.inner?.class)
);
const outerClasses = computed(() =>
twMerge(classes.value.outer?.class, props.pt?.outer?.class)
);
</script>

View File

@ -0,0 +1,19 @@
export type ButtonProps = {
rounded?: boolean;
pt?: PTAttribs;
spinerPlace?: spinerPlace;
};
export type PTAttribs = {
root?: {
class: string;
};
outer?: {
class: string;
};
inner?: {
class: string;
};
};
export type spinerPlace = 'suffix' | 'prefix';

View File

@ -0,0 +1,67 @@
<template>
<div :class="rootPT">
<button
id="dropdownDefaultButton"
data-dropdown-toggle="dropdown"
:class="titlePT"
type="button"
@click="
useRipple($event, props.ripple);
openMenu();
"
>
{{ props.title }}
<span :class="arrowPT">
<ml-arrow></ml-arrow>
</span>
</button>
<div v-show="isMenuOpen" :class="dropdownPT" @click="isOpen = false">
<slot />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { useRipple } from "#imports";
import { twMerge } from "tailwind-merge";
import type { DropDownProps, DropDownPT } from "./types";
const props = withDefaults(defineProps<DropDownProps>(), {
isOpen: false,
});
const PT = {
root: {
class: "",
},
dropdown: {
class: "z-10 absolute bg-white rounded-3xl pointer text-black text-center",
},
title: {
class:
"text-black inline-flex items-center rounded-lg font-medium text-center text-base pointer ",
},
arrow: {
class: "px-2",
},
} as DropDownPT;
const rootPT = computed(() => twMerge(PT.root?.class, props.pt?.root?.class));
const titlePT = computed(() =>
twMerge(PT.title?.class, props.pt?.title?.class)
);
const dropdownPT = computed(() =>
twMerge(PT.dropdown?.class, props.pt?.dropdown?.class)
);
const arrowPT = computed(() =>
twMerge(PT.arrow?.class, props.pt?.arrow?.class)
);
const isOpen = ref(props.isOpen);
function openMenu() {
isOpen.value = !isOpen.value;
}
const isMenuOpen = computed(() => isOpen.value);
</script>

View File

@ -0,0 +1,21 @@
<template>
<div :class="rootPT">
<slot />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { twMerge } from 'tailwind-merge';
import type { DropDownItemPT, DropDownItemProps } from './types';
const props = withDefaults(defineProps<DropDownItemProps>(), {});
const PT = {
root: {
class: ''
}
} as DropDownItemPT;
const rootPT = computed(() => twMerge(PT.root?.class, props.pt?.root?.class));
</script>

View File

@ -0,0 +1,21 @@
export type DropDownProps = {
pt?: DropDownPT;
isOpen?: boolean;
title: string;
ripple?: boolean;
};
export type DropDownPT = {
root?: { class: string };
title?: { class: string };
dropdown?: { class: string };
arrow?: { class: string };
};
export type DropDownItemProps = {
pt?: DropDownItemPT;
};
export type DropDownItemPT = {
root?: { class: string };
};

View File

@ -0,0 +1,68 @@
<template>
<transition name="fade">
<div
v-if="modelValue"
v-block-scroll="modelValue"
class="top-0 left-0 z-[100] fixed flex justify-center items-center bg-gray-900 opacity-80 w-screen h-screen"
@click.self="emitClose"
>
<div class="bg-gray-100 dark:bg-gray-700 border rounded w-1/2">
<div class="flex justify-between p-2 border-b">
<h2 class="pr-4 font-bold">Header title</h2>
<button class="border" @click="emitClose">
<XMarkIcon class="size-6"></XMarkIcon>
</button>
</div>
<div class="p-4">
<slot>
<RocketLaunchIcon class="float-left mr-4 text-green-600 size-20"></RocketLaunchIcon>
<p>content</p>
</slot>
</div>
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import { RocketLaunchIcon, XMarkIcon } from '@heroicons/vue/24/solid';
import type { ModalProps } from './types';
const props = withDefaults(defineProps<ModalProps>(), {});
onMounted(() => {
document.addEventListener('keydown', closeModalKeyBoardEvent);
});
onBeforeUnmount(() => {
document.removeEventListener('keydown', closeModalKeyBoardEvent);
});
function closeModalKeyBoardEvent(event: KeyboardEvent) {
if (event.key === 'Escape') {
emitClose();
}
}
const emit = defineEmits<{
(name: 'update:modelValue', info: boolean): void;
}>();
function emitClose() {
emit('update:modelValue', false);
}
</script>
<style scoped>
.fade-enter-active {
transition: all 0.2s ease-out;
}
.fade-leave-active {
transition: all 0.4s ease-in;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,3 @@
export type ModalProps = {
modelValue?: boolean;
};

View File

@ -0,0 +1,524 @@
<script setup lang="ts">
import { toRef, type PropType, computed, ref, watch, onMounted, defineProps, defineEmits } from 'vue';
import { twMerge } from 'tailwind-merge';
import type { PTAttribs } from './types';
const props = defineProps({
pt: {
type: Object,
default: function () {}
},
items: {
type: Object,
required: true
},
itemsPerPage: {
type: Number,
default: 10,
validator: (value: number) => value > 0 || 'itemsPerPage must be greater than 0.'
},
currentPage: {
type: Number,
default: 10
},
modelValue: {
type: Number,
required: true,
validator: (value: number) => value > 0 || 'v-model must be greater than 0.'
},
maxPagesShown: {
type: Number,
default: 5,
validator: (value: number) => value > 0 || 'maxPagesShown must be greater than 0.'
},
dir: {
type: String as PropType<'ltr' | 'rtl'>,
default: 'ltr',
validator: (value: string) => ['ltr', 'rtl'].includes(value) || 'dir must be either "ltr" or "rtl".'
},
type: {
type: String as PropType<'link' | 'button'>,
default: 'button',
validator: (value: string) => ['link', 'button'].includes(value) || 'type must be "link" or "button".'
},
onClick: Function,
locale: {
type: String as PropType<'en' | 'ar' | 'ir'>,
default: 'en',
validator: (value: string) => ['en', 'ar', 'ir'].includes(value) || 'locale must be "en", "ar", or "ir".'
},
prevButtonContent: {
type: String,
default: '<'
},
nextButtonContent: {
type: String,
default: '>'
},
hidePrevNext: {
type: Boolean,
default: false
},
hidePrevNextWhenEnds: {
type: Boolean,
default: false
},
showBreakpointButtons: {
type: Boolean,
default: true
},
disableBreakpointButtons: {
type: Boolean,
default: false
},
startingBreakpointContent: {
type: String,
default: '...'
},
endingBreakpointButtonContent: {
type: String,
default: '...'
},
showJumpButtons: {
type: Boolean,
default: false
},
linkUrl: {
type: String,
default: '#'
},
backwardJumpButtonContent: {
type: String,
default: '<<'
},
forwardJumpButtonContent: {
type: String,
default: '>>'
},
disablePagination: {
type: Boolean,
default: false
},
showEndingButtons: {
type: Boolean,
default: false
},
firstPageContent: {
type: String,
default: 'First'
},
lastPageContent: {
type: String,
default: 'Last'
},
// Class props
backButtonClass: String,
nextButtonClass: {
type: String,
default: 'next-button'
},
firstButtonClass: {
type: String,
default: 'first-button'
},
lastButtonClass: {
type: String,
default: 'last-button'
},
numberButtonsClass: {
type: String,
default: 'number-buttons'
},
startingBreakpointButtonClass: {
type: String,
default: 'starting-breakpoint-button'
},
endingBreakPointButtonClass: {
type: String,
default: 'ending-breakpoint-button'
},
firstPageButtonClass: {
type: String,
default: 'first-page-button'
},
lastPageButtonClass: {
type: String,
default: 'last-page-button'
},
paginateButtonsClass: {
type: String,
default: 'px-3 py-1 rounded-full font-[1000] text-sm'
},
disabledPaginateButtonsClass: {
type: String,
default: 'disabled-paginate-buttons'
},
disabledBreakPointButtonClass: String,
backwardJumpButtonClass: String,
forwardJumpButtonClass: String,
disabledBackwardJumpButtonClass: String,
disabledBackButtonClass: String,
disabledFirstButtonClass: String,
disabledLastButtonClass: String,
disabledNextButtonClass: String,
disabledForwardJumpButtonClass: String
});
const classes = ref({
root: { class: `` },
paginator: { class: `` },
activePaginator: { class: '' }
} as PTAttribs);
const rootClasses = computed(() => twMerge(classes.value.root?.class, props.pt?.root?.class));
const paginatorClasses = computed(() => twMerge(classes.value.paginator?.class, props.pt?.paginator?.class));
const activePaginator = computed(() => twMerge(classes.value.activePaginator?.class, props.pt?.activePaginator?.class));
if (props.currentPage && !props.modelValue) {
throw new Error('currentPage/current-page is deprecated, use v-model instead to set the current page.');
}
if (!props.modelValue) {
throw new TypeError('v-model is required for the paginate component.');
}
const currentPageRef = toRef(props, 'modelValue');
const emit = defineEmits(['update:modelValue', 'click', 'update:paginatedItems']);
const onClickHandler = (number: number) => {
if (number === currentPageRef.value || number > totalPages.value || number < 1 || props.disablePagination) return;
emit('update:modelValue', number);
emit('click', number);
};
const NumbersLocale = (number: number) => {
switch (props.locale) {
case 'ar':
return number.toLocaleString('ar-SA');
case 'ir':
return number.toLocaleString('fa-IR');
default:
return number;
}
};
const navigationHandler = (page: number) => {
if (props.type !== 'link') return '';
return props.linkUrl.replace('[page]', page.toString());
};
const totalPages = computed(() => Math.ceil(props.items.length / props.itemsPerPage));
const paginatedItems = computed(() => {
const start = (currentPageRef.value - 1) * props.itemsPerPage;
const end = start + props.itemsPerPage;
return props.items.slice(start, end);
});
watch(currentPageRef, emitPaginatedItems);
onMounted(emitPaginatedItems);
function emitPaginatedItems() {
emit('update:paginatedItems', paginatedItems.value);
}
const paginate = computed(() => {
let startPage: number, endPage: number;
if (totalPages.value <= props.maxPagesShown) {
startPage = 1;
endPage = totalPages.value;
} else {
const maxPagesShownBeforeCurrentPage = Math.floor(props.maxPagesShown / 2);
const maxPagesShownAfterCurrentPage = Math.ceil(props.maxPagesShown / 2) - 1;
if (currentPageRef.value <= maxPagesShownBeforeCurrentPage) {
startPage = 1;
endPage = props.maxPagesShown;
} else if (currentPageRef.value + maxPagesShownAfterCurrentPage >= totalPages.value) {
startPage = totalPages.value - props.maxPagesShown + 1;
endPage = totalPages.value;
} else {
startPage = currentPageRef.value - maxPagesShownBeforeCurrentPage;
endPage = currentPageRef.value + maxPagesShownAfterCurrentPage;
}
}
let pages = Array.from(Array(endPage + 1 - startPage).keys()).map((i) => startPage + i);
if (props.dir === 'rtl') {
pages = pages.reverse();
}
return { currentPage: currentPageRef.value, itemsPerPage: props.itemsPerPage, totalPages: totalPages, startPage, endPage, pages };
});
const isRtl = computed(() => props.dir === 'rtl');
const backButtonIfCondition = computed(() =>
isRtl.value ? !props.hidePrevNextWhenEnds || currentPageRef.value !== totalPages.value : !props.hidePrevNextWhenEnds || currentPageRef.value !== 1
);
const nextButtonIfCondition = computed(() =>
isRtl.value ? !props.hidePrevNextWhenEnds || currentPageRef.value !== 1 : !props.hidePrevNextWhenEnds || currentPageRef.value !== totalPages.value
);
const startingBreakPointButtonIfCondition = computed(() => (isRtl.value ? paginate.value.pages[0] < totalPages.value - 1 : paginate.value.pages[0] >= 3));
const endingBreakPointButtonIfCondition = computed(() =>
isRtl.value ? paginate.value.pages[paginate.value.pages.length - 1] >= 3 : paginate.value.pages[paginate.value.pages.length - 1] < totalPages.value - 1
);
const firstButtonIfCondition = computed(() => (isRtl.value ? paginate.value.pages[0] < totalPages.value : paginate.value.pages[0] >= 2));
const lastButtonIfCondition = computed(() =>
isRtl.value ? paginate.value.pages[paginate.value.pages.length - 1] >= 2 : paginate.value.pages[paginate.value.pages.length - 1] < totalPages.value
);
const firstPageButtonIfCondition = computed(() => currentPageRef.value !== 1);
const lastPageButtonIfCondition = computed(() => currentPageRef.value !== totalPages.value);
if (props.type === 'link' && (props.linkUrl === '#' || !props.linkUrl.includes('[page]'))) {
throw new TypeError('linkUrl must contain "[page]" if type is "link".');
}
</script>
<template>
<ul id="componentContainer" :class="rootClasses">
<!-- Go back to first page Button -->
<li v-if="showEndingButtons && firstPageButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? totalPages : 1)"
:class="[firstPageButtonClass, paginatorClasses, disablePagination ? disabledPaginateButtonsClass : '']"
:disabled="disablePagination"
@click="emitPaginatedItems()"
@click.prevent="onClickHandler(isRtl ? totalPages : 1)"
>
<slot name="first-page-button">
{{ firstPageContent }}
</slot>
</component>
</li>
<!-- Backward Jump Button -->
<li v-if="showJumpButtons && startingBreakPointButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? currentPageRef + Math.ceil(maxPagesShown / 2) : currentPageRef - Math.ceil(maxPagesShown / 2))"
:class="[
backwardJumpButtonClass,
paginateButtonsClass,
disablePagination ? disabledPaginateButtonsClass : '',
disablePagination ? disabledBackwardJumpButtonClass : ''
]"
:disabled="disablePagination"
@click.prevent="onClickHandler(isRtl ? currentPageRef + Math.ceil(maxPagesShown / 2) : currentPageRef - Math.ceil(maxPagesShown / 2))"
@click="emitPaginatedItems()"
>
<slot name="backward-jump-button">
{{ backwardJumpButtonContent }}
</slot>
</component>
</li>
<!-- Back Button -->
<li v-if="!hidePrevNext && backButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? currentPageRef + 1 : currentPageRef - 1)"
:class="[
backButtonClass,
paginateButtonsClass,
disablePagination ? disabledPaginateButtonsClass : '',
disablePagination ? disabledBackButtonClass : ''
]"
:disabled="disablePagination"
@click.prevent="onClickHandler(isRtl ? currentPageRef + 1 : currentPageRef - 1)"
@click="emitPaginatedItems()"
>
<slot name="prev-button">
{{ prevButtonContent }}
</slot>
</component>
</li>
<!-- First Button before Starting Breakpoint Button -->
<li v-if="showBreakpointButtons && firstButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? totalPages : 1)"
:class="[
firstButtonClass,
paginateButtonsClass,
disablePagination ? disabledPaginateButtonsClass : '',
disablePagination ? disabledFirstButtonClass : ''
]"
:disabled="disablePagination"
@click.prevent="onClickHandler(isRtl ? totalPages : 1)"
@click="emitPaginatedItems()"
>
{{ isRtl ? NumbersLocale(totalPages) : NumbersLocale(1) }}
</component>
</li>
<!-- Starting Breakpoint Button -->
<li v-if="showBreakpointButtons && startingBreakPointButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="
navigationHandler(
disableBreakpointButtons
? currentPageRef
: isRtl
? currentPageRef + Math.ceil(maxPagesShown / 2)
: currentPageRef - Math.ceil(maxPagesShown / 2)
)
"
:disabled="disableBreakpointButtons || disablePagination"
:class="[
startingBreakpointButtonClass,
paginateButtonsClass,
disableBreakpointButtons || disablePagination ? `${disabledPaginateButtonsClass} ${disabledBreakPointButtonClass}` : ''
]"
@click="emitPaginatedItems()"
@click.prevent="
onClickHandler(
disableBreakpointButtons
? currentPageRef
: isRtl
? currentPageRef + Math.ceil(maxPagesShown / 2)
: currentPageRef - Math.ceil(maxPagesShown / 2)
)
"
>
<slot name="starting-breakpoint-button">
{{ startingBreakpointContent }}
</slot>
</component>
</li>
<!-- Numbers Buttons -->
<li v-for="(page, index) in paginate.pages" :key="index">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(page)"
:class="[
paginateButtonsClass,
numberButtonsClass,
page === currentPageRef ? activePaginator : '',
disablePagination ? disabledPaginateButtonsClass : ''
]"
:disabled="disablePagination"
@click.prevent="() => onClickHandler(page)"
@click="emitPaginatedItems()"
>
{{ NumbersLocale(page) }}
</component>
</li>
<!-- Ending Breakpoint Button -->
<li v-if="showBreakpointButtons && endingBreakPointButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="
navigationHandler(
disableBreakpointButtons
? currentPageRef
: isRtl
? currentPageRef - Math.ceil(maxPagesShown / 2)
: currentPageRef + Math.ceil(maxPagesShown / 2)
)
"
:disabled="disableBreakpointButtons || disablePagination"
:class="[
endingBreakPointButtonClass,
paginateButtonsClass,
disableBreakpointButtons || disablePagination ? `${disabledPaginateButtonsClass} ${disabledBreakPointButtonClass}` : ''
]"
@click="emitPaginatedItems()"
@click.prevent="
onClickHandler(
disableBreakpointButtons
? currentPageRef
: isRtl
? currentPageRef - Math.ceil(maxPagesShown / 2)
: currentPageRef + Math.ceil(maxPagesShown / 2)
)
"
>
<slot name="ending-breakpoint-button">
{{ endingBreakpointButtonContent }}
</slot>
</component>
</li>
<!-- Last Button after Ending Breakingpoint Button-->
<li v-if="showBreakpointButtons && lastButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? 1 : totalPages)"
:class="[
lastButtonClass,
paginateButtonsClass,
disablePagination ? disabledPaginateButtonsClass : '',
disablePagination ? disabledLastButtonClass : ''
]"
:disabled="disablePagination"
@click="emitPaginatedItems()"
@click.prevent="onClickHandler(isRtl ? 1 : totalPages)"
>
{{ isRtl ? NumbersLocale(1) : NumbersLocale(totalPages) }}
</component>
</li>
<!-- Next Button -->
<li v-if="!hidePrevNext && nextButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? currentPageRef - 1 : currentPageRef + 1)"
:class="[
paginateButtonsClass,
nextButtonClass,
disablePagination ? disabledPaginateButtonsClass : '',
disablePagination ? disabledNextButtonClass : ''
]"
:disabled="disablePagination"
@click.prevent="onClickHandler(isRtl ? currentPageRef - 1 : currentPageRef + 1)"
@click="emitPaginatedItems()"
>
<slot name="next-button">
{{ nextButtonContent }}
</slot>
</component>
</li>
<!-- Forward Jump Button -->
<li v-if="showJumpButtons && endingBreakPointButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? currentPageRef - Math.ceil(maxPagesShown / 2) : currentPageRef + Math.ceil(maxPagesShown / 2))"
:class="[
forwardJumpButtonClass,
paginateButtonsClass,
disablePagination ? disabledPaginateButtonsClass : '',
disablePagination ? disabledForwardJumpButtonClass : ''
]"
:disabled="disablePagination"
@click.prevent="onClickHandler(isRtl ? currentPageRef - Math.ceil(maxPagesShown / 2) : currentPageRef + Math.ceil(maxPagesShown / 2))"
@click="emitPaginatedItems()"
>
<slot name="forward-jump-button">
{{ forwardJumpButtonContent }}
</slot>
</component>
</li>
<!-- Go forward to last page -->
<li v-if="showEndingButtons && lastPageButtonIfCondition">
<component
:is="type === 'button' ? 'button' : 'a'"
:href="navigationHandler(isRtl ? 1 : totalPages)"
:class="[lastPageButtonClass, paginateButtonsClass, disablePagination ? disabledPaginateButtonsClass : '']"
:disabled="disablePagination"
@click.prevent="onClickHandler(isRtl ? 1 : totalPages)"
@click="emitPaginatedItems()"
>
<slot name="last-page-button">
{{ lastPageContent }}
</slot>
</component>
</li>
</ul>
</template>

View File

@ -0,0 +1,11 @@
export type PTAttribs = {
root?: {
class: string;
};
paginator?: {
class: string;
};
activePaginator?: {
class: string;
};
};

View File

@ -0,0 +1,143 @@
<template>
<div v-show="showSwiper" class="relative" :class="rootClasses">
<swiper
loop-add-blank-slides
:class="sliderClasses"
:direction="direction"
:breakpoints="{
450: { slidesPerView: 1.2, spaceBetween: 20 },
500: { slidesPerView: 1.5, spaceBetween: 20 },
600: { slidesPerView: 1.5, spaceBetween: 20 },
770: { slidesPerView: 1, spaceBetween: 20 },
800: { slidesPerView: 1, spaceBetween: 20 },
1000: { slidesPerView: 1.2, spaceBetween: 20 },
1200: { slidesPerView: 1.5, spaceBetween: 20 },
1600: { slidesPerView: 2, spaceBetween: 40 },
2000: { slidesPerView: 2.5, spaceBetween: 40 },
}"
:space-between="50"
@swiper="onSwiper"
@slide-change="onSlideChange"
>
<swiper-slide
v-for="item in items"
:key="item.title"
class="hover:cursor-pointer"
>
<slot :item="item" />
</swiper-slide>
</swiper>
<div>
<div :class="counterClasses">
<div :class="progressClass" :style="`width: ${progerssBar}%`"></div>
<div class="flex justify-between my-4">
<button
:disabled="isPrevDisabled"
class="px-4 py-2 disabled:text-gray-400 cursor-pointer"
@click="
useRipple($event, true);
goPrev();
"
>
<ArrowLeftIcon class="size-5" />
</button>
<button
:disabled="isNextDisabled"
class="px-4 py-2 disabled:text-gray-400 cursor-pointer"
@click="
useRipple($event, true);
goNext();
"
>
<ArrowRightIcon class="size-5" />
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// import { useRipple } from "#imports";
import { ref, computed, defineProps, withDefaults } from "vue";
import { Swiper, SwiperSlide } from "swiper/vue";
import type { Swiper as swiper } from "swiper/types";
import "swiper/css";
import { twMerge } from "tailwind-merge";
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/vue/24/solid";
import type { PTAttribs, SliderProps } from "./types";
const showSwiper = ref(false);
const progerssBar = ref(0);
const prevDisabled = ref(true);
const nextDisabled = ref(true);
let swiperInstance: swiper;
const onSwiper = (swiper: swiper) => {
progerssBar.value = Math.ceil(
((swiper.realIndex + (swiper.params.slidesPerView as number)) /
swiper.el.querySelectorAll(".swiper-slide").length) *
100
);
swiperInstance = swiper;
showSwiper.value = true;
prevDisabled.value = !swiper.params.loop && swiper.realIndex == 0;
nextDisabled.value = swiperInstance.isEnd;
};
const onSlideChange = (swiper: swiper) => {
progerssBar.value = Math.ceil(
((swiper.realIndex + (swiper.params.slidesPerView as number)) /
swiper.slides.length) *
100
);
prevDisabled.value = !swiper.params.loop && swiper.realIndex == 0;
nextDisabled.value = swiper.isEnd;
};
const isPrevDisabled = computed(() => prevDisabled.value);
const isNextDisabled = computed(() => nextDisabled.value);
function goNext() {
swiperInstance.slideNext();
}
function goPrev() {
swiperInstance.slidePrev();
}
const props = withDefaults(defineProps<SliderProps>(), {
items: [],
});
const classes = ref({
root: {
class: ``,
},
slider: {
class: ``,
},
counter: {
class: ``,
},
progress: {
class: "",
},
} as unknown as PTAttribs);
const rootClasses = computed(() =>
twMerge(classes.value.root?.class, props.pt?.root?.class)
);
const sliderClasses = computed(() =>
twMerge(classes.value.slider?.class, props.pt?.slider?.class)
);
const counterClasses = computed(() =>
twMerge(classes.value.counter?.class, props.pt?.counter?.class)
);
const progressClass = computed(() =>
twMerge(classes.value.progress?.class, props.pt?.progress?.class)
);
const items = props.items;
</script>

View File

@ -0,0 +1,25 @@
export type SliderProps = {
direction: "horizontal" | "vertical";
pt?: PTAttribs;
items: Array<[{ title: string; info: string }]>;
};
export type PTAttribs = {
root?: {
class: string;
};
slider?: {
class: string;
};
counter?: {
class: string;
};
direction?: {
direction: string;
};
progress?: {
class: string;
};
};
export type spinerPlace = "suffix" | "prefix";

View File

@ -0,0 +1,111 @@
<template>
<div v-show="showSwiper" class="relative" :class="rootClasses">
<swiper
loop-add-blank-slides
:class="sliderClasses"
:direction="direction"
:space-between="50"
:breakpoints="{
2000: { slidesPerView: 1, spaceBetween: 40 },
}"
@swiper="onSwiper"
@slide-change="onSlideChange"
>
<swiper-slide
v-for="slide in 4"
:key="slide"
class="h-full hover:cursor-pointer"
>
<slot />
</swiper-slide>
</swiper>
<div :class="counterClasses">
<div
v-for="(slide, index) in 4"
:key="index"
class="bg-black"
:style="[
'height: 3px; transition: all 0.5s;',
index === currentSlideNumber
? 'padding-top : 8px; padding-bottom: 8px; width: 16px; border-radius : 50%;'
: 'height: 3px; width: 25px;',
]"
@click="goToSlide(index), stopAutoSlide()"
></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, defineProps, withDefaults, onMounted } from "vue";
import { Swiper, SwiperSlide } from "swiper/vue";
import type { Swiper as swiper } from "swiper/types";
import "swiper/css";
import { twMerge } from "tailwind-merge";
import type { PTAttribs } from "./types";
const showSwiper = ref(false);
const currentSlideNumber = ref(0);
let swiperInstance: swiper | null = null;
let intervalId: NodeJS.Timeout | null = null;
const onSwiper = (swiper: swiper) => {
swiperInstance = swiper;
showSwiper.value = true;
};
const onSlideChange = (swiper: swiper) => {
currentSlideNumber.value = swiper.realIndex;
};
function startAutoSlide() {
intervalId = setInterval(() => {
const nextSlide = (currentSlideNumber.value + 1) % 4;
goToSlide(nextSlide);
}, 5000);
}
function goToSlide(index: number) {
swiperInstance?.slideTo(index);
currentSlideNumber.value = index;
}
function stopAutoSlide() {
if (intervalId !== null) {
clearInterval(intervalId);
intervalId = null;
}
}
onMounted(() => {
startAutoSlide();
});
const props = withDefaults(
defineProps<{
direction: "horizontal" | "vertical";
pt?: PTAttribs;
}>(),
{
direction: "horizontal",
}
);
const classes = ref({
root: { class: `` },
slider: { class: `` },
counter: {
class: `mx-auto px-6 container flex justify-between items-center h-10 cursor-pointer`,
},
} as PTAttribs);
const rootClasses = computed(() =>
twMerge(classes.value.root?.class, props.pt?.root?.class)
);
const sliderClasses = computed(() =>
twMerge(classes.value.slider?.class, props.pt?.slider?.class)
);
const counterClasses = computed(() =>
twMerge(classes.value.counter?.class, props.pt?.counter?.class)
);
</script>

View File

@ -0,0 +1,21 @@
export type SliderProps = {
rounded?: boolean;
pt?: PTAttribs;
};
export type PTAttribs = {
root?: {
class: string;
};
slider?: {
class: string;
};
counter?: {
class: string;
};
direction?: {
direction: string;
};
};
export type spinerPlace = 'suffix' | 'prefix';

View File

@ -0,0 +1,30 @@
<template>
<svg :class="spinnerClasses" fill="none" role="status" viewBox="0 0 100 101" xmlns="http://www.w3.org/2000/svg">
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
</template>
<script lang="ts" setup>
import { toRefs } from 'vue';
import { useSpinnerClasses } from './composables/useSpinnerClasses';
import type { SpinnerColor, SpinnerSize } from './types';
interface ISpinnerProps {
color?: SpinnerColor;
size?: SpinnerSize;
}
const props = withDefaults(defineProps<ISpinnerProps>(), {
color: 'blue',
size: '4'
});
const { spinnerClasses } = useSpinnerClasses(toRefs(props));
</script>

View File

@ -0,0 +1,52 @@
import { computed, type Ref } from 'vue';
import classNames from 'classnames';
import type { SpinnerColor, SpinnerSize } from '../types';
const sizes: Record<SpinnerSize, string> = {
0: 'w-0 h-0',
0.5: 'w-0.5 h-0.5',
1: 'w-1 h-1',
1.5: 'w-1.5 h-1.5',
10: 'w-10 h-10',
11: 'w-11 h-11',
12: 'w-12 h-12',
2: 'w-2 h-2',
2.5: 'w-2.5 h-2.5',
3: 'w-3 h-3',
4: 'w-4 h-4',
5: 'w-5 h-5',
6: 'w-6 h-6',
7: 'w-7 h-7',
8: 'w-8 h-8',
9: 'w-9 h-9'
};
const colors: Record<SpinnerColor, string> = {
blue: 'fill-blue-600',
gray: 'fill-gray-600 dark:fill-gray-300',
green: 'fill-green-500',
pink: 'fill-pink-600',
purple: 'fill-purple-600',
red: 'fill-red-600',
white: 'fill-white',
yellow: 'fill-yellow-400',
accent1: 'fill-accent1',
accent2: 'fill-accent2'
};
export type UseSpinnerClassesProps = {
color: Ref<SpinnerColor>;
size: Ref<SpinnerSize>;
};
export function useSpinnerClasses(props: UseSpinnerClassesProps): {
spinnerClasses: Ref<string>;
} {
const sizeClasses = computed(() => sizes[props.size.value]);
const colorClasses = computed(() => colors[props.color.value]);
const bgColorClasses = computed(() => 'text-gray-200 dark:text-gray-400');
const animateClasses = computed(() => 'animate-spin');
const spinnerClasses = computed(() => classNames(animateClasses.value, bgColorClasses.value, colorClasses.value, sizeClasses.value));
return { spinnerClasses };
}

View File

@ -0,0 +1,3 @@
export type SpinnerColor = 'blue' | 'gray' | 'green' | 'red' | 'yellow' | 'pink' | 'purple' | 'white' | 'accent1' | 'accent2';
export type SpinnerSize = '0' | '0.5' | '1' | '1.5' | '2' | '2.5' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12';

View File

@ -0,0 +1,41 @@
<template>
<div class="flex md:flex-row flex-col text-black">
<div class="flex flex-col justify-between cursor-pointer w-[40%]">
<div
v-for="(el, index) in props.tabs"
:key="index"
class="w-full px-6 py-4 md:py-8 rtl:text-right text-left border-b border-gray-400 md:text-[18px] md:last:border-b-0 md:h-full flex items-center"
@click="
showContent(index);
useRipple($event, true);
"
>
{{ el.header }}
</div>
</div>
<div v-show="tabs[indexItem]" class="md:flex-1 h-auto">
<div class="flex flex-col items-start">
<!-- Your image -->
<h1 class="font-bold text-2xl md:text-[32px]">
{{ tabs[indexItem].content.title }}
</h1>
<p class="text-[16px] md:text-[18px]">
{{ tabs[indexItem].content.info }}
</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const indexItem = ref(0);
function showContent(index) {
indexItem.value = index;
}
const props = defineProps({
tabs: Object,
});
</script>

View File

@ -0,0 +1,34 @@
<template>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.25 8.75C15.5 8.75 15 8.25 15 7.5V6.25C15 5.5 15.5 5 16.25 5C17 5 17.5 5.5 17.5 6.25V7.5C17.5 8.25 17 8.75 16.25 8.75Z" fill="white" />
<path
d="M7.375 12.375C7 12.375 6.75 12.25 6.5 12L3.875 9.375C3.375 8.875 3.375 8.125 3.875 7.625C4.375 7.125 5.125 7.125 5.625 7.625L8.25 10.25C8.75 10.75 8.75 11.5 8.25 12C8 12.25 7.75 12.375 7.375 12.375Z"
fill="white"
/>
<path
d="M3.75 21.25H2.5C1.75 21.25 1.25 20.75 1.25 20C1.25 19.25 1.75 18.75 2.5 18.75H3.75C4.5 18.75 5 19.25 5 20C5 20.75 4.5 21.25 3.75 21.25Z"
fill="white"
/>
<path
d="M4.75 32.75C4.375 32.75 4.125 32.625 3.875 32.375C3.375 31.875 3.375 31.125 3.875 30.625L6.5 28C7 27.5 7.75 27.5 8.25 28C8.75 28.5 8.75 29.25 8.25 29.75L5.625 32.375C5.375 32.625 5.125 32.75 4.75 32.75Z"
fill="white"
/>
<path
d="M16.25 35C15.5 35 15 34.5 15 33.75V32.5C15 31.75 15.5 31.25 16.25 31.25C17 31.25 17.5 31.75 17.5 32.5V33.75C17.5 34.5 17 35 16.25 35Z"
fill="white"
/>
<path
d="M27.5 31.25C21.25 31.25 16.25 26.25 16.25 20C16.25 13.75 21.25 8.75 27.5 8.75C33.75 8.75 38.75 13.75 38.75 20C38.75 26.25 33.75 31.25 27.5 31.25Z"
fill="white"
/>
<path
d="M13.75 20C13.75 16.125 15.375 12.625 17.875 10.125C17.375 10 16.875 10 16.25 10C10.75 10 6.25 14.5 6.25 20C6.25 25.5 10.75 30 16.25 30C16.875 30 17.375 30 17.875 29.875C15.375 27.375 13.75 23.875 13.75 20Z"
fill="white"
/>
<path
d="M12.4083 25.2228L12.4083 25.2228L12.4123 25.2249C13.409 25.758 14.5207 26.02 15.7361 26.02C16.9515 26.02 18.0632 25.758 19.0599 25.2249L19.0599 25.2249L19.0639 25.2228C20.0574 24.6798 20.8437 23.9298 21.4133 22.9765C21.9859 22.0182 22.2681 20.9417 22.2681 19.76C22.2681 18.5783 21.9859 17.5018 21.4133 16.5435C20.8435 15.5899 20.0565 14.8446 19.0619 14.3122C18.0649 13.7678 16.9525 13.5 15.7361 13.5C14.5197 13.5 13.4073 13.7678 12.4103 14.3122C11.4145 14.8452 10.6276 15.5966 10.0582 16.5607C9.48652 17.5179 9.2041 18.5885 9.2041 19.76C9.2041 20.932 9.48673 22.0073 10.0575 22.9742L10.0575 22.9742L10.0589 22.9765C10.6285 23.9298 11.4148 24.6798 12.4083 25.2228ZM18.7241 17.9888L18.7241 17.9888L18.726 17.9921C19.0205 18.5075 19.1721 19.0927 19.1721 19.76C19.1721 20.4265 19.0208 21.0181 18.725 21.5456C18.4286 22.0637 18.0248 22.4676 17.507 22.7642C16.9913 23.049 16.4049 23.196 15.7361 23.196C15.0673 23.196 14.4809 23.049 13.9652 22.7642C13.4474 22.4676 13.0436 22.0636 12.7472 21.5456C12.4514 21.018 12.3001 20.4265 12.3001 19.76C12.3001 19.0927 12.4517 18.5075 12.7462 17.9921L12.7462 17.9921L12.7481 17.9888C13.045 17.4599 13.448 17.0581 13.9623 16.7734L13.9623 16.7735L13.9682 16.7701C14.4836 16.4756 15.0688 16.324 15.7361 16.324C16.4034 16.324 16.9886 16.4756 17.504 16.7701L17.504 16.7702L17.5099 16.7734C18.0242 17.0581 18.4272 17.4599 18.7241 17.9888ZM24.5214 25.63L24.5232 25.6306C25.3817 25.8904 26.2679 26.02 27.18 26.02C28.2095 26.02 29.1139 25.8637 29.8764 25.5301C30.6249 25.2026 31.216 24.7483 31.6113 24.1504C32.009 23.5651 32.208 22.9077 32.208 22.192C32.208 21.3782 31.9911 20.6727 31.5061 20.1322C31.0798 19.6459 30.5603 19.2854 29.9538 19.0542C29.3896 18.8289 28.6826 18.6202 27.84 18.4256C27.2391 18.278 26.775 18.1486 26.4421 18.0377C26.1428 17.9379 25.9115 17.8084 25.7355 17.6576C25.6257 17.5566 25.568 17.4339 25.568 17.248C25.568 16.9628 25.6772 16.7439 25.9449 16.5549C26.2284 16.3622 26.7208 16.228 27.5 16.228C27.9513 16.228 28.4232 16.2952 28.9172 16.4335C29.408 16.5709 29.8803 16.7724 30.3349 17.0392L30.8337 17.332L31.0513 16.7961L31.7073 15.1801L31.8607 14.8022L31.5214 14.576C30.987 14.2198 30.3628 13.954 29.6569 13.7719C28.9548 13.5907 28.2408 13.5 27.516 13.5C26.4865 13.5 25.5821 13.6563 24.8196 13.9899L24.8196 13.9899L24.8169 13.9911C24.0754 14.3206 23.4873 14.7816 23.084 15.3866L23.0826 15.3887C22.6964 15.9743 22.504 16.6306 22.504 17.344C22.504 18.1649 22.7135 18.8778 23.1837 19.4286L23.1836 19.4287L23.1924 19.4385C23.6275 19.922 24.1457 20.2825 24.7432 20.5142L24.7432 20.5142L24.7464 20.5154C25.3073 20.7285 26.0229 20.9368 26.8865 21.142C27.4881 21.2898 27.9527 21.4193 28.2859 21.5303C28.5819 21.629 28.802 21.7605 28.9632 21.9141L28.972 21.9225L28.9812 21.9304C29.1019 22.0347 29.16 22.156 29.16 22.336C29.16 22.5784 29.0607 22.7823 28.7662 22.9711L28.7661 22.971L28.7612 22.9743C28.4771 23.1612 27.9762 23.292 27.18 23.292C26.5661 23.292 25.9489 23.1901 25.3267 22.9832C24.709 22.7635 24.2012 22.4897 23.7941 22.1678L23.2892 21.7686L23.0273 22.3565L22.3073 23.9725L22.1562 24.3117L22.4372 24.5544C22.974 25.018 23.677 25.3711 24.5214 25.63Z"
fill="black"
stroke="white"
/>
</svg>
</template>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,31 @@
<template>
<span class="flex min-w-8 cursor-pointer" @click="chanegMode">
<ClientOnly>
<span :title="formatedModeName">
<DarkComponent v-if="colormode.value == 'dark'" class="w-8" />
<LightComponent v-if="colormode.value == 'light'" class="w-8" />
</span>
</ClientOnly>
</span>
</template>
<script setup lang="ts">
import LightComponent from './MlLight.vue';
import DarkComponent from './MlDark.vue';
const colormode = useColorMode();
function chanegMode(): void {
if (colormode.preference == 'dark') {
colormode.preference = 'light';
} else {
colormode.preference = 'dark';
}
}
const formatedModeName = computed(() => {
if (colormode.value.length > 0) {
return colormode.value.charAt(0).toUpperCase() + colormode.value.slice(1);
} else {
return '';
}
});
</script>

View File

@ -0,0 +1,7 @@
import type { DefineComponent } from 'vue';
export type DarkMode = {
name: 'Light' | 'Dark';
value: 'light' | 'dark';
icon: DefineComponent;
};

View File

@ -0,0 +1,48 @@
<script lang="ts">
import { h, Transition } from 'vue';
export default defineNuxtComponent({
// name: 'MaalTransitionFade',
props: {
name: {
type: String,
default: 'fade'
},
duration: {
type: Number,
default: 300
},
delay: {
type: Number,
default: 200
}
},
setup(props, { slots }) {
return () => {
return h(
Transition,
{
name: props.name,
css: false,
onBeforeEnter(el: any) {
el.style.transition = `opacity ${props.duration}ms ${props.delay}ms ease;`;
el.style.opacity = 0;
},
onEnter(el: any, done) {
requestAnimationFrame(() => {
el.style.opacity = 1;
done();
});
},
onLeave(el: any, done) {
el.style.transition = `opacity ${props.duration}ms ${props.delay}ms ease;`;
el.style.opacity = 0;
setTimeout(done, props.duration);
}
},
slots.default
);
};
}
});
</script>