Merge pull request #80 from cogor/avatar-component

Added an avatar component (Issue #51)
This commit is contained in:
Ilya Artamonov
2022-09-16 11:22:53 +03:00
committed by GitHub
17 changed files with 440 additions and 22 deletions

View File

@@ -35,6 +35,7 @@ function buildSidebar() {
function getComponents() {
return [
{ text: 'Alert', link: '/components/alert/alert.md' },
{ text: 'Avatar', link: 'components/avatar/avatar.md' },
{ text: 'Button', link: '/components/button/button.md' },
{ text: 'Button Group', link: '/components/buttonGroup/buttonGroup.md' },
{ text: 'Dropdown', link: '/components/dropdown/dropdown.md' },
@@ -44,7 +45,6 @@ function getComponents() {
{ text: 'Toast', link: 'components/toast/toast.md' },
{ text: '- Accordion', link: 'components/accordion/accordion.md' },
{ text: '- Avatar', link: 'components/avatar/avatar.md' },
{ text: '- Badge', link: 'components/badge/badge.md' },
{ text: '- Breadcrumb', link: 'components/breadcrumb/breadcrumb.md' },
{ text: '- Card', link: 'components/card/card.md' },

View File

@@ -1,15 +1,187 @@
<script setup>
import AvatarExample from './examples/AvatarExample.vue'
import AvatarBorderedExample from './examples/AvatarBorderedExample.vue'
import AvatarDotIndicatorExample from './examples/AvatarDotIndicatorExample.vue'
import AvatarSizeExample from './examples/AvatarSizeExample.vue'
import AvatarDotIndicatorPositionExample from './examples/AvatarDotIndicatorPositionExample.vue'
import AvatarAlternativeTextExample from './examples/AvatarAlternativeTextExample.vue'
import StackedAvatarsExample from './examples/StackedAvatarsExample.vue'
import AvatarPlaceholderExample from './examples/AvatarPlaceholderExample.vue'
import AvatarPlaceholderInitialsExample from './examples/AvatarPlaceholderInitialsExample.vue'
</script>
# Avatar
Use the avatar component to show a visual representation of a user profile using an image element or SVG object based on multiple styles and sizes
## Default avatar
Use this example to create a circle and rounded avatar on an image element.
<AvatarExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<Avatar></Avatar>
<div class="flex">
<Avatar status="online" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" />
<Avatar status="online" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" />
</div>
</template>
```
<AvatarExample />
## Bordered
Use this example to create a circle and rounded avatar on an image element.
<AvatarBorderedExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<div class="flex">
<Avatar status="online" bordered img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" bordered rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
</div>
</template>
```
## Dot indicator
Use a dot element relative to the avatar component as an indicator for the user (eg. online or offline status).
<AvatarDotIndicatorExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<div class="flex">
<Avatar status="online" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="busy" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="away" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="offline" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
</div>
</template>
```
## Dot indicator position
<AvatarDotIndicatorPositionExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<div class="flex items-center">
<Avatar status="online" status-position="top-left" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="top-left" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="top-right" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="top-right" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-left" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-left" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-right" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-right" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
</div>
</template>
```
## Sizes
Choose from multiple sizing options for the avatar component from this example.
<AvatarSizeExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<div class="flex items-center">
<Avatar size="xs" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="sm" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="md" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="lg" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="xl" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
</div>
</template>
```
## Alternative text
<AvatarAlternativeTextExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<Avatar status="online" alt="Alternative text" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg"/>
</template>
```
## Stacked avatars
Use this example if you want to stack a group of users by overlapping the avatar components.
<StackedAvatarsExample />
```vue
<script setup>
import { StackedAvatars, Avatar, StackedAvatarsCounter } from 'flowbite-vue'
</script>
<template>
<StackedAvatars>
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-1.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-2.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-3.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-4.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" rounded />
</StackedAvatars>
<StackedAvatars class="mt-2.5">
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-1.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-2.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-3.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-4.jpg" rounded />
<StackedAvatarsCounter total="99" href="#" />
</StackedAvatars>
</template>
```
## Placeholder icon
<AvatarPlaceholderExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<div class="flex">
<Avatar class="mr-2.5" />
<Avatar rounded />
</div>
</template>
```
## Placeholder initials
<AvatarPlaceholderInitialsExample />
```vue
<script setup>
import { Avatar } from 'flowbite-vue'
</script>
<template>
<div class="flex">
<Avatar initials="JD" class="mr-2.5" />
<Avatar initials="JD" rounded />
</div>
</template>
```

View File

@@ -0,0 +1,8 @@
<template>
<div class="vp-raw flex">
<Avatar status="online" alt="Alternative text" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg"/>
</div>
</template>
<script setup>
import { Avatar } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="vp-raw flex">
<Avatar status="online" bordered img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" bordered rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
</div>
</template>
<script setup>
import { Avatar } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="vp-raw flex">
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" status="online" />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" status="busy" />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" status="away" />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" status="offline" />
</div>
</template>
<script setup>
import { Avatar } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,15 @@
<template>
<div class="vp-raw flex">
<Avatar status="online" status-position="top-left" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="top-left" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="top-right" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="top-right" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-left" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-left" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-right" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" status-position="bottom-right" rounded img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
</div>
</template>
<script setup>
import { Avatar } from '../../../../src/index'
</script>

View File

@@ -1,6 +1,7 @@
<template>
<div class="vp-raw flex flex-col">
<Avatar></Avatar>
<div class="vp-raw flex">
<Avatar status="online" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar status="online" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" rounded />
</div>
</template>
<script setup>

View File

@@ -0,0 +1,9 @@
<template>
<div class="vp-raw flex">
<Avatar class="mr-2.5" />
<Avatar rounded />
</div>
</template>
<script setup>
import { Avatar } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="vp-raw flex">
<Avatar initials="JD" class="mr-2.5" />
<Avatar initials="JD" rounded />
</div>
</template>
<script setup>
import { Avatar } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,14 @@
<template>
<div class="vp-raw">
<div class="flex items-center">
<Avatar size="xs" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="sm" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="md" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="lg" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
<Avatar size="xl" img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" class="mr-2.5" />
</div>
</div>
</template>
<script setup>
import { Avatar } from '../../../../src/index'
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div class="vp-raw flex-col">
<StackedAvatars>
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-1.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-2.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-3.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-4.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" rounded />
</StackedAvatars>
<StackedAvatars class="mt-2.5">
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-1.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-2.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-3.jpg" rounded />
<Avatar stacked img="https://flowbite.com/docs/images/people/profile-picture-4.jpg" rounded />
<StackedAvatarsCounter total="99" href="#" />
</StackedAvatars>
</div>
</template>
<script setup>
import { StackedAvatars, Avatar, StackedAvatarsCounter } from '../../../../src/index'
</script>

View File

@@ -1,27 +1,30 @@
<template>
<div>
<!-- <img class="w-10 h-10 rounded-full" src="/docs/images/people/profile-picture-5.jpg" alt="Rounded avatar">-->
<div class="relative">
<div v-if="!img" :class="avatarPlaceholderWrapperClasses">
<svg v-if="!initials" :class="avatarPlaceholderClasses" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
</svg>
<div v-else :class="avatarPlaceholderInitialsClasses">{{ initials }}</div>
</div>
<img v-else :class="avatarClasses" :src="img" :alt="alt">
<span v-if="status" :class="avatarDotClasses" :data-pos="statusPosition"></span>
</div>
</template>
<script lang="ts" setup>
import { computed, toRefs } from 'vue'
import { toRefs } from 'vue'
import type { PropType } from 'vue'
import type { AvatarSize, AvatarStatus, AvatarStatusPosition } from './types'
import { useAvatarClasses } from '@/components/Avatar/composables/useAvatarClasses'
const props = defineProps({
alt: {
type: String,
default: '',
default: 'Avatar',
},
bordered: {
type: Boolean,
default: false,
},
children: {
type: Array,
default() {
return []
},
},
img: {
type: String,
default: '',
@@ -31,7 +34,7 @@ const props = defineProps({
default: false,
},
size: {
type: String, // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
type: String as PropType<AvatarSize>,
default: 'md',
},
stacked: {
@@ -39,13 +42,19 @@ const props = defineProps({
default: false,
},
status: {
type: String, // 'away' | 'busy' | 'offline' | 'online';
default: '',
type: String as PropType<AvatarStatus>,
default: null,
},
statusPosition: {
type: String, // 'bottom-left' | 'bottom-right' | 'bottom-center' | 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right'
default: 'top-left',
type: String as PropType<AvatarStatusPosition>,
default: 'top-right',
},
initials: {
type: String,
default: null,
},
})
const { avatarClasses, avatarDotClasses, avatarPlaceholderClasses, avatarPlaceholderWrapperClasses, avatarPlaceholderInitialsClasses } = useAvatarClasses(toRefs(props))
</script>

View File

@@ -0,0 +1,5 @@
<template>
<div class="flex -space-x-4">
<slot name="default" />
</div>
</template>

View File

@@ -0,0 +1,15 @@
<template>
<a class="relative flex justify-center items-center w-10 h-10 text-xs font-medium text-white bg-gray-700 rounded-full border-2 border-white hover:bg-gray-600 dark:border-gray-800" :href="href">+{{ total }}</a>
</template>
<script setup>
defineProps({
total: {
type: Number,
default: 1,
},
href: {
type: String,
default: '#',
},
})
</script>

View File

@@ -0,0 +1,113 @@
import { computed } from 'vue'
import type { Ref } from 'vue'
import classNames from 'classnames'
import type { AvatarSize, AvatarStatus, AvatarStatusPosition, AvatarType, avatarDotIndicatorPositionClasses } from '@/components/Avatar/types'
const avatarSizeClasses: Record<AvatarSize, string> = {
xs: 'w-6 h-6',
sm: 'w-8 h-8',
md: 'w-10 h-10',
lg: 'w-20 h-20',
xl: 'w-36 h-36',
}
const avatarTypeClasses: Record<AvatarType, string> = {
default: 'rounded',
rounded: 'rounded-full',
}
const avatarBorderedClasses = 'ring-2 ring-gray-300 dark:ring-gray-500 p-1'
const avatarStatusDotDefaultClasses = 'absolute h-3.5 w-3.5 rounded-full border-2 border-white dark:border-gray-800'
const avatarStatusDotClasses: Record<AvatarStatus, string> = {
away: 'bg-gray-400',
busy: 'bg-yellow-400',
offline: 'bg-red-400',
online: 'bg-green-400',
}
const avatarStatusDotPositionClasses: Record<avatarDotIndicatorPositionClasses, string> = {
'top-right-rounded': 'top-0 -right-0.5',
'top-right-default': '-top-1.5 -right-1.5',
'top-left-rounded': 'top-0 left-0',
'top-left-default': 'top-0 left-0 transform -translate-y-1/2 -translate-x-1/2',
'bottom-right-rounded': 'bottom-0 -right-0.5',
'bottom-right-default': 'bottom-0 -right-1.5 translate-y-1/2',
'bottom-left-rounded': 'bottom-0 left-0',
'bottom-left-default': '-bottom-1.5 left-0 transform -translate-x-1/2 ',
}
const avatarPlaceholderDefaultClasses = 'absolute w-auto h-auto text-gray-400'
const avatarPlaceholderWrapperDefaultClasses = 'inline-flex overflow-hidden relative justify-center items-center bg-gray-100 dark:bg-gray-600'
const avatarPlaceholderInitialsDefaultClasses = 'font-medium text-gray-600 dark:text-gray-300'
const avatarPlaceholderSizes = {
xs: 'bottom-0',
sm: 'bottom-0',
md: '-bottom-1',
lg: '-bottom-2',
xl: '-bottom-4',
}
export type UseAvatarClassesProps = {
status: Ref<AvatarStatus>
bordered: Ref<boolean>
img: Ref<string>
alt: Ref<string>
rounded: Ref<boolean>
size: Ref<AvatarSize>
stacked: Ref<boolean>
statusPosition: Ref<AvatarStatusPosition>
}
export function useAvatarClasses(props: UseAvatarClassesProps): {
avatarClasses: Ref<string>
avatarDotClasses: Ref<string>
avatarPlaceholderClasses: Ref<string>
avatarPlaceholderWrapperClasses: Ref<string>
avatarPlaceholderInitialsClasses: Ref<string>
} {
const avatarClasses = computed<string>(() => {
console.log('border', props.bordered.value)
return classNames(
avatarSizeClasses[props.size.value],
avatarTypeClasses[props.rounded.value ? 'rounded' : 'default'],
props.bordered.value ? avatarBorderedClasses : '',
props.stacked.value ? 'border-2 border-white dark:border-gray-800' : '',
)
})
const avatarDotClasses = computed<string>(() => {
const avatarType = `${props.statusPosition.value}-${props.rounded.value ? 'rounded' : 'default'}`
return classNames(
avatarStatusDotDefaultClasses,
avatarStatusDotClasses[props.status.value],
avatarStatusDotPositionClasses[avatarType as avatarDotIndicatorPositionClasses],
)
})
const avatarPlaceholderClasses = computed<string>(() => {
return classNames(
avatarPlaceholderDefaultClasses,
avatarPlaceholderSizes[props.size.value],
)
})
const avatarPlaceholderWrapperClasses = computed<string>(() => {
return classNames(
avatarPlaceholderWrapperDefaultClasses,
avatarSizeClasses[props.size.value],
avatarTypeClasses[props.rounded.value ? 'rounded' : 'default'],
)
})
const avatarPlaceholderInitialsClasses = computed<string>(() => {
return classNames(
avatarPlaceholderInitialsDefaultClasses,
)
})
// TODO: Avatar Initials
return {
avatarClasses,
avatarDotClasses,
avatarPlaceholderClasses,
avatarPlaceholderWrapperClasses,
avatarPlaceholderInitialsClasses,
}
}

View File

@@ -0,0 +1,5 @@
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
export type AvatarStatus = 'away' | 'busy' | 'offline' | 'online'
export type AvatarStatusPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
export type AvatarType = 'default' | 'rounded'
export type avatarDotIndicatorPositionClasses = `${AvatarStatusPosition}-${AvatarType}`

View File

@@ -7,9 +7,11 @@ export { default as Tab } from './components/Tabs/components/Tab/Tab.vue'
export { default as Dropdown } from './components/Dropdown/Dropdown.vue'
export { default as FlowbiteThemable } from './components/utils/FlowbiteThemable/FlowbiteThemable.vue'
export { default as FlowbiteThemableChild } from './components/utils/FlowbiteThemable/components/FlowbiteThemableChild/FlowbiteThemableChild.vue'
export { default as Accordion } from './components/Accordion/Accordion.vue'
export { default as Avatar } from './components/Avatar/Avatar.vue'
export { default as StackedAvatars } from './components/Avatar/StackedAvatars.vue'
export { default as StackedAvatarsCounter } from './components/Avatar/StackedAvatarsCounter.vue'
export { default as Accordion } from './components/Accordion/Accordion.vue'
export { default as Badge } from './components/Badge/Badge.vue'
export { default as Breadcrumb } from './components/Breadcrumb/Breadcrumb.vue'
export { default as Card } from './components/Card/Card.vue'