replace some element plus components

This commit is contained in:
pa
2026-01-15 18:29:06 +09:00
committed by Natsumi
parent 75980b7062
commit bdc1d3a347
33 changed files with 986 additions and 89 deletions

View File

@@ -73,14 +73,14 @@
<span>{{ t('nav_menu.whats_new') }}</span>
</button>
</div>
<el-divider></el-divider>
<Separator />
<div class="nav-menu-support__section">
<span class="nav-menu-support__title">{{ t('nav_menu.resources') }}</span>
<button type="button" class="nav-menu-settings__item" @click="handleSupportLink('wiki')">
<span>{{ t('nav_menu.wiki') }}</span>
</button>
</div>
<el-divider></el-divider>
<Separator />
<div class="nav-menu-support__section">
<span class="nav-menu-support__title">{{ t('nav_menu.get_help') }}</span>
<button type="button" class="nav-menu-settings__item" @click="handleSupportLink('github')">
@@ -129,7 +129,7 @@
<span class="nav-menu-settings__version">{{ version }}</span>
</div>
</div>
<el-divider></el-divider>
<Separator />
<button type="button" class="nav-menu-settings__item" @click="handleSettingsClick">
<span>{{ t('nav_tooltip.settings') }}</span>
</button>
@@ -156,7 +156,7 @@
<span v-if="themeMode === theme" class="nav-menu-theme__check"></span>
</button>
<el-divider></el-divider>
<Separator />
<el-popover
v-model:visible="themeColorMenuVisible"
@@ -247,6 +247,7 @@
<script setup>
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { dayjs } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@@ -752,10 +753,6 @@
</script>
<style scoped>
:deep(.el-divider) {
margin: 0;
}
.nav-menu-container {
position: relative;
width: 100%;

View File

@@ -194,25 +194,21 @@
(userDialog.representedGroup && userDialog.representedGroup.isRepresenting)
"
class="extra">
<div style="display: inline-block; flex: none; margin-right: 5px">
<el-image
v-loading="userDialog.isRepresentedGroupLoading"
class="x-link"
:src="userDialog.representedGroup.$thumbnailUrl"
style="
flex: none;
width: 60px;
height: 60px;
border-radius: 4px;
object-fit: cover;
"
<div
v-loading="userDialog.isRepresentedGroupLoading"
style="display: inline-block; flex: none; margin-right: 5px">
<Avatar
class="x-link size-15! rounded-lg!"
:style="{
background: userDialog.isRepresentedGroupLoading ? '#f5f7fa' : ''
}"
@load="userDialog.isRepresentedGroupLoading = false"
@click="showFullscreenImageDialog(userDialog.representedGroup.iconUrl)">
<template #error></template>
</el-image>
<AvatarImage
:src="userDialog.representedGroup.$thumbnailUrl"
@load="userDialog.isRepresentedGroupLoading = false"
@error="userDialog.isRepresentedGroupLoading = false" />
<AvatarFallback class="rounded-lg!" />
</Avatar>
</div>
<span
v-if="userDialog.representedGroup.isRepresenting"
@@ -1358,6 +1354,7 @@
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Spinner } from '@/components/ui/spinner';

View File

@@ -0,0 +1,16 @@
<script setup>
import { AvatarRoot } from 'reka-ui';
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<AvatarRoot
data-slot="avatar"
:class="cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', props.class)">
<slot />
</AvatarRoot>
</template>

View File

@@ -0,0 +1,23 @@
<script setup>
import { AvatarFallback } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
delayMs: { type: Number, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
</script>
<template>
<AvatarFallback
data-slot="avatar-fallback"
v-bind="delegatedProps"
:class="cn('bg-muted flex size-full items-center justify-center rounded-full', props.class)">
<slot />
</AvatarFallback>
</template>

View File

@@ -0,0 +1,17 @@
<script setup>
import { AvatarImage } from 'reka-ui';
const props = defineProps({
src: { type: String, required: true },
referrerPolicy: { type: null, required: false },
crossOrigin: { type: null, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false }
});
</script>
<template>
<AvatarImage data-slot="avatar-image" v-bind="props" class="aspect-square size-full">
<slot />
</AvatarImage>
</template>

View File

@@ -0,0 +1,3 @@
export { default as Avatar } from './Avatar.vue';
export { default as AvatarFallback } from './AvatarFallback.vue';
export { default as AvatarImage } from './AvatarImage.vue';

View File

@@ -0,0 +1,211 @@
<script setup>
import { CalendarRoot, useDateFormatter, useForwardPropsEmits } from 'reka-ui';
import { createReusableTemplate, reactiveOmit, useVModel } from '@vueuse/core';
import { createYear, createYearRange, toDate } from 'reka-ui/date';
import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select';
import { computed, toRaw } from 'vue';
import { getLocalTimeZone, today } from '@internationalized/date';
import { cn } from '@/lib/utils';
import {
CalendarCell,
CalendarCellTrigger,
CalendarGrid,
CalendarGridBody,
CalendarGridHead,
CalendarGridRow,
CalendarHeadCell,
CalendarHeader,
CalendarHeading,
CalendarNextButton,
CalendarPrevButton
} from '.';
const props = defineProps({
defaultValue: { type: null, required: false },
defaultPlaceholder: { type: null, required: false },
placeholder: { type: null, required: false },
pagedNavigation: { type: Boolean, required: false },
preventDeselect: { type: Boolean, required: false },
weekStartsOn: { type: Number, required: false },
weekdayFormat: { type: String, required: false },
calendarLabel: { type: String, required: false },
fixedWeeks: { type: Boolean, required: false },
maxValue: { type: null, required: false },
minValue: { type: null, required: false },
locale: { type: String, required: false },
numberOfMonths: { type: Number, required: false },
disabled: { type: Boolean, required: false },
readonly: { type: Boolean, required: false },
initialFocus: { type: Boolean, required: false },
isDateDisabled: { type: Function, required: false },
isDateUnavailable: { type: Function, required: false },
dir: { type: String, required: false },
nextPage: { type: Function, required: false },
prevPage: { type: Function, required: false },
modelValue: { type: null, required: false, default: undefined },
multiple: { type: Boolean, required: false },
disableDaysOutsideCurrentView: { type: Boolean, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false },
layout: { type: null, required: false, default: undefined },
yearRange: { type: Array, required: false }
});
const emits = defineEmits(['update:modelValue', 'update:placeholder']);
const delegatedProps = reactiveOmit(props, 'class', 'layout', 'placeholder');
const placeholder = useVModel(props, 'placeholder', emits, {
passive: true,
defaultValue: props.defaultPlaceholder ?? today(getLocalTimeZone())
});
const formatter = useDateFormatter(props.locale ?? 'en');
const yearRange = computed(() => {
return (
props.yearRange ??
createYearRange({
start:
props?.minValue ??
(toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone())).cycle(
'year',
-100
),
end:
props?.maxValue ??
(toRaw(props.placeholder) ?? props.defaultPlaceholder ?? today(getLocalTimeZone())).cycle(
'year',
10
)
})
);
});
const [DefineMonthTemplate, ReuseMonthTemplate] = createReusableTemplate();
const [DefineYearTemplate, ReuseYearTemplate] = createReusableTemplate();
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<DefineMonthTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative">
<div class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none">
{{ formatter.custom(toDate(date), { month: 'short' }) }}
</div>
<NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="
(e) => {
placeholder = placeholder.set({
month: Number(e?.target?.value)
});
}
">
<NativeSelectOption
v-for="month in createYear({ dateObj: date })"
:key="month.toString()"
:value="month.month"
:selected="date.month === month.month">
{{ formatter.custom(toDate(month), { month: 'short' }) }}
</NativeSelectOption>
</NativeSelect>
</div>
</div>
</DefineMonthTemplate>
<DefineYearTemplate v-slot="{ date }">
<div class="**:data-[slot=native-select-icon]:right-1">
<div class="relative">
<div class="absolute inset-0 flex h-full items-center text-sm pl-2 pointer-events-none">
{{ formatter.custom(toDate(date), { year: 'numeric' }) }}
</div>
<NativeSelect
class="text-xs h-8 pr-6 pl-2 text-transparent relative"
@change="
(e) => {
placeholder = placeholder.set({
year: Number(e?.target?.value)
});
}
">
<NativeSelectOption
v-for="year in yearRange"
:key="year.toString()"
:value="year.year"
:selected="date.year === year.year">
{{ formatter.custom(toDate(year), { year: 'numeric' }) }}
</NativeSelectOption>
</NativeSelect>
</div>
</div>
</DefineYearTemplate>
<CalendarRoot
v-slot="{ grid, weekDays, date }"
v-bind="forwarded"
v-model:placeholder="placeholder"
data-slot="calendar"
:class="cn('p-3', props.class)">
<CalendarHeader class="pt-0">
<nav class="flex items-center gap-1 absolute top-0 inset-x-0 justify-between">
<CalendarPrevButton>
<slot name="calendar-prev-icon" />
</CalendarPrevButton>
<CalendarNextButton>
<slot name="calendar-next-icon" />
</CalendarNextButton>
</nav>
<slot name="calendar-heading" :date="date" :month="ReuseMonthTemplate" :year="ReuseYearTemplate">
<template v-if="layout === 'month-and-year'">
<div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" />
<ReuseYearTemplate :date="date" />
</div>
</template>
<template v-else-if="layout === 'month-only'">
<div class="flex items-center justify-center gap-1">
<ReuseMonthTemplate :date="date" />
{{ formatter.custom(toDate(date), { year: 'numeric' }) }}
</div>
</template>
<template v-else-if="layout === 'year-only'">
<div class="flex items-center justify-center gap-1">
{{ formatter.custom(toDate(date), { month: 'short' }) }}
<ReuseYearTemplate :date="date" />
</div>
</template>
<template v-else>
<CalendarHeading />
</template>
</slot>
</CalendarHeader>
<div class="flex flex-col gap-y-4 mt-4 sm:flex-row sm:gap-x-4 sm:gap-y-0">
<CalendarGrid v-for="month in grid" :key="month.value.toString()">
<CalendarGridHead>
<CalendarGridRow>
<CalendarHeadCell v-for="day in weekDays" :key="day">
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody>
<CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="mt-2 w-full">
<CalendarCell v-for="weekDate in weekDates" :key="weekDate.toString()" :date="weekDate">
<CalendarCellTrigger :day="weekDate" :month="month.value" />
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</template>

View File

@@ -0,0 +1,30 @@
<script setup>
import { CalendarCell, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
date: { type: null, required: true },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarCell
data-slot="calendar-cell"
:class="
cn(
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent',
props.class
)
"
v-bind="forwardedProps">
<slot />
</CalendarCell>
</template>

View File

@@ -0,0 +1,42 @@
<script setup>
import { CalendarCellTrigger, useForwardProps } from 'reka-ui';
import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
day: { type: null, required: true },
month: { type: null, required: true },
asChild: { type: Boolean, required: false },
as: { type: null, required: false, default: 'button' },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarCellTrigger
data-slot="calendar-cell-trigger"
:class="
cn(
buttonVariants({ variant: 'ghost' }),
'size-8 p-0 font-normal aria-selected:opacity-100 cursor-default',
'[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground',
// Selected
'data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground',
// Disabled
'data-[disabled]:text-muted-foreground data-[disabled]:opacity-50',
// Unavailable
'data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through',
// Outside months
'data-[outside-view]:text-muted-foreground',
props.class
)
"
v-bind="forwardedProps">
<slot />
</CalendarCellTrigger>
</template>

View File

@@ -0,0 +1,24 @@
<script setup>
import { CalendarGrid, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarGrid
data-slot="calendar-grid"
:class="cn('w-full border-collapse space-x-1', props.class)"
v-bind="forwardedProps">
<slot />
</CalendarGrid>
</template>

View File

@@ -0,0 +1,14 @@
<script setup>
import { CalendarGridBody } from 'reka-ui';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false }
});
</script>
<template>
<CalendarGridBody data-slot="calendar-grid-body" v-bind="props">
<slot />
</CalendarGridBody>
</template>

View File

@@ -0,0 +1,15 @@
<script setup>
import { CalendarGridHead } from 'reka-ui';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
</script>
<template>
<CalendarGridHead data-slot="calendar-grid-head" v-bind="props">
<slot />
</CalendarGridHead>
</template>

View File

@@ -0,0 +1,21 @@
<script setup>
import { CalendarGridRow, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarGridRow data-slot="calendar-grid-row" :class="cn('flex', props.class)" v-bind="forwardedProps">
<slot />
</CalendarGridRow>
</template>

View File

@@ -0,0 +1,24 @@
<script setup>
import { CalendarHeadCell, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarHeadCell
data-slot="calendar-head-cell"
:class="cn('text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem]', props.class)"
v-bind="forwardedProps">
<slot />
</CalendarHeadCell>
</template>

View File

@@ -0,0 +1,24 @@
<script setup>
import { CalendarHeader, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarHeader
data-slot="calendar-header"
:class="cn('flex justify-center pt-1 relative items-center w-full px-8', props.class)"
v-bind="forwardedProps">
<slot />
</CalendarHeader>
</template>

View File

@@ -0,0 +1,29 @@
<script setup>
import { CalendarHeading, useForwardProps } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
defineSlots();
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarHeading
v-slot="{ headingValue }"
data-slot="calendar-heading"
:class="cn('text-sm font-medium', props.class)"
v-bind="forwardedProps">
<slot :heading-value>
{{ headingValue }}
</slot>
</CalendarHeading>
</template>

View File

@@ -0,0 +1,35 @@
<script setup>
import { CalendarNext, useForwardProps } from 'reka-ui';
import { ChevronRight } from 'lucide-vue-next';
import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
nextPage: { type: Function, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarNext
data-slot="calendar-next-button"
:class="
cn(
buttonVariants({ variant: 'outline' }),
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class
)
"
v-bind="forwardedProps">
<slot>
<ChevronRight class="size-4" />
</slot>
</CalendarNext>
</template>

View File

@@ -0,0 +1,35 @@
<script setup>
import { CalendarPrev, useForwardProps } from 'reka-ui';
import { ChevronLeft } from 'lucide-vue-next';
import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
prevPage: { type: Function, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
const forwardedProps = useForwardProps(delegatedProps);
</script>
<template>
<CalendarPrev
data-slot="calendar-prev-button"
:class="
cn(
buttonVariants({ variant: 'outline' }),
'size-7 bg-transparent p-0 opacity-50 hover:opacity-100',
props.class
)
"
v-bind="forwardedProps">
<slot>
<ChevronLeft class="size-4" />
</slot>
</CalendarPrev>
</template>

View File

@@ -0,0 +1,12 @@
export { default as Calendar } from './Calendar.vue';
export { default as CalendarCell } from './CalendarCell.vue';
export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue';
export { default as CalendarGrid } from './CalendarGrid.vue';
export { default as CalendarGridBody } from './CalendarGridBody.vue';
export { default as CalendarGridHead } from './CalendarGridHead.vue';
export { default as CalendarGridRow } from './CalendarGridRow.vue';
export { default as CalendarHeadCell } from './CalendarHeadCell.vue';
export { default as CalendarHeader } from './CalendarHeader.vue';
export { default as CalendarHeading } from './CalendarHeading.vue';
export { default as CalendarNextButton } from './CalendarNextButton.vue';
export { default as CalendarPrevButton } from './CalendarPrevButton.vue';

View File

@@ -0,0 +1,46 @@
<script setup>
import { reactiveOmit, useVModel } from '@vueuse/core';
import { ChevronDownIcon } from 'lucide-vue-next';
import { cn } from '@/lib/utils';
defineOptions({
inheritAttrs: false
});
const props = defineProps({
modelValue: { type: null, required: false },
class: { type: null, required: false }
});
const emit = defineEmits(['update:modelValue']);
const modelValue = useVModel(props, 'modelValue', emit, {
passive: true,
defaultValue: ''
});
const delegatedProps = reactiveOmit(props, 'class');
</script>
<template>
<div class="group/native-select relative w-fit has-[select:disabled]:opacity-50" data-slot="native-select-wrapper">
<select
v-bind="{ ...$attrs, ...delegatedProps }"
v-model="modelValue"
data-slot="native-select"
:class="
cn(
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
props.class
)
">
<slot />
</select>
<ChevronDownIcon
class="text-muted-foreground pointer-events-none absolute top-1/2 right-3.5 size-4 -translate-y-1/2 opacity-50 select-none"
aria-hidden="true"
data-slot="native-select-icon" />
</div>
</template>

View File

@@ -0,0 +1,16 @@
<!-- @fallthroughAttributes true -->
<!-- @strictTemplates true -->
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<optgroup data-slot="native-select-optgroup" :class="cn('bg-popover text-popover-foreground', props.class)">
<slot />
</optgroup>
</template>

View File

@@ -0,0 +1,16 @@
<!-- @fallthroughAttributes true -->
<!-- @strictTemplates true -->
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<option data-slot="native-select-option" :class="cn('bg-popover text-popover-foreground', props.class)">
<slot />
</option>
</template>

View File

@@ -0,0 +1,3 @@
export { default as NativeSelect } from './NativeSelect.vue';
export { default as NativeSelectOptGroup } from './NativeSelectOptGroup.vue';
export { default as NativeSelectOption } from './NativeSelectOption.vue';

View File

@@ -0,0 +1,11 @@
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<div data-slot="skeleton" :class="cn('animate-pulse rounded-md bg-primary/10', props.class)" />
</template>

View File

@@ -0,0 +1 @@
export { default as Skeleton } from './Skeleton.vue';