feat: Updates on new components (#10444)
This commit is contained in:
@@ -5,37 +5,30 @@ import Avatar from './Avatar.vue';
|
||||
<template>
|
||||
<Story title="Components/Avatar" :layout="{ type: 'grid', width: '400' }">
|
||||
<Variant title="Default">
|
||||
<div class="p-4 bg-white dark:bg-slate-900">
|
||||
<div class="flex p-4 space-x-4 bg-white dark:bg-slate-900">
|
||||
<Avatar
|
||||
name=""
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Amaya"
|
||||
class="bg-ruby-300 dark:bg-ruby-900"
|
||||
/>
|
||||
<Avatar name="Amaya" src="" />
|
||||
<Avatar name="" src="" />
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Default with upload">
|
||||
<div class="p-4 bg-white dark:bg-slate-900">
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Amaya"
|
||||
class="bg-ruby-300 dark:bg-ruby-900"
|
||||
allow-upload
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Invalid or empty SRC">
|
||||
<div class="p-4 space-x-4 bg-white dark:bg-slate-900">
|
||||
<Avatar src="https://example.com/ruby.png" name="Ruby" allow-upload />
|
||||
<Avatar name="Bruce Wayne" allow-upload />
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Rounded Full">
|
||||
<div class="p-4 space-x-4 bg-white dark:bg-slate-900">
|
||||
<Variant title="Different Shapes">
|
||||
<div class="gap-4 p-4 bg-white dark:bg-slate-900">
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Amaya"
|
||||
name=""
|
||||
allow-upload
|
||||
rounded-full
|
||||
:size="48"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Amaya"
|
||||
name=""
|
||||
allow-upload
|
||||
:size="48"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
@@ -43,24 +36,78 @@ import Avatar from './Avatar.vue';
|
||||
<Variant title="Different Sizes">
|
||||
<div class="flex flex-wrap gap-4 p-4 bg-white dark:bg-slate-900">
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Felix"
|
||||
src="https://api.dicebear.com/9.x/avataaars/svg?seed=Felix"
|
||||
:size="48"
|
||||
class="bg-green-300 dark:bg-green-900"
|
||||
name=""
|
||||
allow-upload
|
||||
/>
|
||||
<Avatar
|
||||
:size="72"
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Jade"
|
||||
class="bg-indigo-300 dark:bg-indigo-900"
|
||||
src="https://api.dicebear.com/9.x/avataaars/svg?seed=Jade"
|
||||
name=""
|
||||
allow-upload
|
||||
/>
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Emery"
|
||||
src="https://api.dicebear.com/9.x/avataaars/svg?seed=Emery"
|
||||
name=""
|
||||
:size="96"
|
||||
class="bg-woot-300 dark:bg-woot-900"
|
||||
allow-upload
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="With Status">
|
||||
<div class="flex flex-wrap gap-4 p-4 bg-white dark:bg-slate-900">
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Felix"
|
||||
status="online"
|
||||
name="Felix Online"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Jade"
|
||||
status="busy"
|
||||
name="Jade Busy"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Emery"
|
||||
status="offline"
|
||||
name="Emery Offline"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="With Custom Icon">
|
||||
<div class="flex flex-wrap gap-4 p-4 bg-white dark:bg-slate-900">
|
||||
<Avatar name="Custom Icon" icon-name="i-lucide-user" :size="48" />
|
||||
<Avatar
|
||||
name="Custom Industry"
|
||||
icon-name="i-lucide-building-2"
|
||||
:size="48"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Upload States">
|
||||
<div class="flex flex-wrap gap-4 p-4 bg-white dark:bg-slate-900">
|
||||
<!-- Empty state with upload -->
|
||||
<Avatar name="Upload New" allow-upload :size="48" />
|
||||
|
||||
<!-- With image and upload -->
|
||||
<Avatar
|
||||
src="https://api.dicebear.com/9.x/thumbs/svg?seed=Upload"
|
||||
name="Replace Image"
|
||||
allow-upload
|
||||
:size="48"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Name Initials">
|
||||
<div class="flex flex-wrap gap-4 p-4 bg-white dark:bg-slate-900">
|
||||
<Avatar name="Catherine" :size="48" />
|
||||
<Avatar name="John Doe" :size="48" />
|
||||
<Avatar name="Rose Doe John" :size="48" />
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
@@ -39,11 +39,12 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['upload']);
|
||||
const emit = defineEmits(['upload', 'delete']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const isImageValid = ref(true);
|
||||
const fileInput = ref(null);
|
||||
|
||||
const AVATAR_COLORS = {
|
||||
dark: [
|
||||
@@ -131,13 +132,39 @@ const iconStyles = computed(() => ({
|
||||
}));
|
||||
|
||||
const initialsStyles = computed(() => ({
|
||||
fontSize: `${props.size / 1.8}px`,
|
||||
fontSize: `${props.size / 2}px`,
|
||||
}));
|
||||
|
||||
const invalidateCurrentImage = () => {
|
||||
isImageValid.value = false;
|
||||
};
|
||||
|
||||
const handleUploadAvatar = () => {
|
||||
fileInput.value.click();
|
||||
};
|
||||
|
||||
const handleImageUpload = event => {
|
||||
const [file] = event.target.files;
|
||||
if (file) {
|
||||
emit('upload', {
|
||||
file,
|
||||
url: file ? URL.createObjectURL(file) : null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteAvatar = () => {
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = null;
|
||||
}
|
||||
emit('delete');
|
||||
};
|
||||
|
||||
const handleDismiss = event => {
|
||||
event.stopPropagation();
|
||||
handleDeleteAvatar();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.src,
|
||||
() => {
|
||||
@@ -147,7 +174,7 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="relative inline-flex" :style="containerStyles">
|
||||
<span class="relative inline-flex group/avatar" :style="containerStyles">
|
||||
<!-- Status Badge -->
|
||||
<slot name="badge" :size="size">
|
||||
<div
|
||||
@@ -158,6 +185,15 @@ watch(
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<!-- Delete Avatar Button -->
|
||||
<div
|
||||
v-if="src && allowUpload"
|
||||
class="absolute z-20 flex items-center justify-center invisible w-6 h-6 transition-all duration-300 ease-in-out opacity-0 cursor-pointer outline outline-1 outline-n-container -top-2 -right-2 rounded-xl bg-n-solid-3 group-hover/avatar:visible group-hover/avatar:opacity-100"
|
||||
@click="handleDismiss"
|
||||
>
|
||||
<Icon icon="i-lucide-x" class="text-n-slate-11 size-4" />
|
||||
</div>
|
||||
|
||||
<!-- Avatar Container -->
|
||||
<span
|
||||
role="img"
|
||||
@@ -202,16 +238,23 @@ watch(
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Upload Overlay -->
|
||||
<!-- Upload Overlay and Input -->
|
||||
<div
|
||||
v-if="allowUpload"
|
||||
role="button"
|
||||
class="absolute inset-0 flex items-center justify-center invisible w-full h-full transition-all duration-500 ease-in-out opacity-0 rounded-xl dark:bg-slate-900/50 bg-slate-900/20 group-hover/avatar:visible group-hover/avatar:opacity-100"
|
||||
@click="emit('upload')"
|
||||
class="absolute inset-0 z-10 flex items-center justify-center invisible w-full h-full transition-all duration-300 ease-in-out opacity-0 rounded-xl bg-n-alpha-black1 group-hover/avatar:visible group-hover/avatar:opacity-100"
|
||||
@click="handleUploadAvatar"
|
||||
>
|
||||
<Icon
|
||||
icon="i-lucide-upload"
|
||||
class="text-white dark:text-white size-4"
|
||||
class="text-white"
|
||||
:style="{ width: `${size / 2}px`, height: `${size / 2}px` }"
|
||||
/>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
class="hidden"
|
||||
@change="handleImageUpload"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<script setup>
|
||||
import EditableAvatar from './EditableAvatar.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story title="Components/Avatar" :layout="{ type: 'grid', width: '400' }">
|
||||
<Variant title="Default">
|
||||
<div class="p-4 bg-white dark:bg-slate-900">
|
||||
<EditableAvatar
|
||||
src="https://api.dicebear.com/9.x/avataaars/svg?seed=Amaya"
|
||||
class="bg-ruby-300 dark:bg-ruby-900"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant title="Different Sizes">
|
||||
<div class="flex flex-wrap gap-4 p-4 bg-white dark:bg-slate-900">
|
||||
<EditableAvatar
|
||||
src="https://api.dicebear.com/9.x/avataaars/svg?seed=Felix"
|
||||
:size="48"
|
||||
class="bg-green-300 dark:bg-green-900"
|
||||
/>
|
||||
<EditableAvatar
|
||||
:size="72"
|
||||
src="https://api.dicebear.com/9.x/avataaars/svg?seed=Jade"
|
||||
class="bg-indigo-300 dark:bg-indigo-900"
|
||||
/>
|
||||
<EditableAvatar
|
||||
src="https://api.dicebear.com/9.x/avataaars/svg?seed=Emery"
|
||||
:size="96"
|
||||
class="bg-woot-300 dark:bg-woot-900"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -1,105 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 72,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['upload', 'delete']);
|
||||
|
||||
const avatarSize = computed(() => `${props.size}px`);
|
||||
const iconSize = computed(() => `${props.size / 2}px`);
|
||||
|
||||
const fileInput = ref(null);
|
||||
const imgError = ref(false);
|
||||
|
||||
const shouldShowImage = computed(() => props.src && !imgError.value);
|
||||
|
||||
const handleUploadAvatar = () => {
|
||||
fileInput.value.click();
|
||||
};
|
||||
|
||||
const handleImageUpload = event => {
|
||||
const [file] = event.target.files;
|
||||
if (file) {
|
||||
emit('upload', {
|
||||
file,
|
||||
url: file ? URL.createObjectURL(file) : null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteAvatar = () => {
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = null;
|
||||
}
|
||||
emit('delete');
|
||||
};
|
||||
|
||||
const handleDismiss = event => {
|
||||
event.stopPropagation();
|
||||
handleDeleteAvatar();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative flex flex-col items-center gap-2 select-none rounded-xl outline outline-1 outline-n-container group/avatar"
|
||||
:style="{ width: avatarSize, height: avatarSize }"
|
||||
>
|
||||
<img
|
||||
v-if="shouldShowImage"
|
||||
:src="src"
|
||||
:alt="name || 'avatar'"
|
||||
class="object-cover w-full h-full shadow-sm rounded-xl"
|
||||
@error="imgError = true"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center justify-center w-full h-full rounded-xl bg-n-alpha-2"
|
||||
>
|
||||
<Icon
|
||||
icon="i-lucide-building-2"
|
||||
class="text-n-brand/50"
|
||||
:style="{ width: `${iconSize}`, height: `${iconSize}` }"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="src"
|
||||
class="absolute z-20 outline outline-1 outline-n-container flex items-center cursor-pointer justify-center w-6 h-6 transition-all invisible opacity-0 duration-500 ease-in-out -top-2.5 -right-2.5 rounded-xl bg-n-solid-3 group-hover/avatar:visible group-hover/avatar:opacity-100"
|
||||
@click="handleDismiss"
|
||||
>
|
||||
<Icon icon="i-lucide-x" class="text-n-slate-11 size-4" />
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 z-10 flex items-center justify-center invisible w-full h-full transition-all duration-500 ease-in-out opacity-0 rounded-xl bg-n-alpha-black1 group-hover/avatar:visible group-hover/avatar:opacity-100"
|
||||
@click="handleUploadAvatar"
|
||||
>
|
||||
<Icon
|
||||
icon="i-lucide-upload"
|
||||
class="text-white"
|
||||
:style="{ width: `${iconSize}`, height: `${iconSize}` }"
|
||||
/>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
class="hidden"
|
||||
@change="handleImageUpload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user