diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 0e81b73..6819390 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -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: 'Badge', link: 'components/badge/badge.md' }, { text: 'Button Group', link: '/components/buttonGroup/buttonGroup.md' }, diff --git a/docs/components/avatar/avatar.md b/docs/components/avatar/avatar.md index 63b1c84..a1b8a7f 100644 --- a/docs/components/avatar/avatar.md +++ b/docs/components/avatar/avatar.md @@ -1,15 +1,187 @@ # 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. + + ```vue ``` - +## Bordered +Use this example to create a circle and rounded avatar on an image element. + + + +```vue + + +``` + +## Dot indicator +Use a dot element relative to the avatar component as an indicator for the user (eg. online or offline status). + + + +```vue + + +``` + +## Dot indicator position + + + + +```vue + + +``` + +## Sizes + +Choose from multiple sizing options for the avatar component from this example. + + + +```vue + + +``` + +## Alternative text + + + +```vue + + + +``` + +## Stacked avatars +Use this example if you want to stack a group of users by overlapping the avatar components. + + + +```vue + + + +``` + +## Placeholder icon + + + +```vue + + + +``` + +## Placeholder initials + + + +```vue + + + +``` diff --git a/docs/components/avatar/examples/AvatarAlternativeTextExample.vue b/docs/components/avatar/examples/AvatarAlternativeTextExample.vue new file mode 100644 index 0000000..3b32d2a --- /dev/null +++ b/docs/components/avatar/examples/AvatarAlternativeTextExample.vue @@ -0,0 +1,8 @@ + + diff --git a/docs/components/avatar/examples/AvatarBorderedExample.vue b/docs/components/avatar/examples/AvatarBorderedExample.vue new file mode 100644 index 0000000..1de342b --- /dev/null +++ b/docs/components/avatar/examples/AvatarBorderedExample.vue @@ -0,0 +1,9 @@ + + diff --git a/docs/components/avatar/examples/AvatarDotIndicatorExample.vue b/docs/components/avatar/examples/AvatarDotIndicatorExample.vue new file mode 100644 index 0000000..ed38dd8 --- /dev/null +++ b/docs/components/avatar/examples/AvatarDotIndicatorExample.vue @@ -0,0 +1,11 @@ + + diff --git a/docs/components/avatar/examples/AvatarDotIndicatorPositionExample.vue b/docs/components/avatar/examples/AvatarDotIndicatorPositionExample.vue new file mode 100644 index 0000000..5deae90 --- /dev/null +++ b/docs/components/avatar/examples/AvatarDotIndicatorPositionExample.vue @@ -0,0 +1,15 @@ + + diff --git a/docs/components/avatar/examples/AvatarExample.vue b/docs/components/avatar/examples/AvatarExample.vue index 316eb82..a0705b8 100644 --- a/docs/components/avatar/examples/AvatarExample.vue +++ b/docs/components/avatar/examples/AvatarExample.vue @@ -1,6 +1,7 @@ diff --git a/docs/components/avatar/examples/AvatarPlaceholderInitialsExample.vue b/docs/components/avatar/examples/AvatarPlaceholderInitialsExample.vue new file mode 100644 index 0000000..a53b0f4 --- /dev/null +++ b/docs/components/avatar/examples/AvatarPlaceholderInitialsExample.vue @@ -0,0 +1,9 @@ + + diff --git a/docs/components/avatar/examples/AvatarSizeExample.vue b/docs/components/avatar/examples/AvatarSizeExample.vue new file mode 100644 index 0000000..cdaf1a6 --- /dev/null +++ b/docs/components/avatar/examples/AvatarSizeExample.vue @@ -0,0 +1,14 @@ + + diff --git a/docs/components/avatar/examples/StackedAvatarsExample.vue b/docs/components/avatar/examples/StackedAvatarsExample.vue new file mode 100644 index 0000000..60f25f6 --- /dev/null +++ b/docs/components/avatar/examples/StackedAvatarsExample.vue @@ -0,0 +1,21 @@ + + diff --git a/src/components/Avatar/Avatar.vue b/src/components/Avatar/Avatar.vue index b56b350..1d674fe 100644 --- a/src/components/Avatar/Avatar.vue +++ b/src/components/Avatar/Avatar.vue @@ -1,27 +1,30 @@ diff --git a/src/components/Avatar/StackedAvatars.vue b/src/components/Avatar/StackedAvatars.vue new file mode 100644 index 0000000..ef4c830 --- /dev/null +++ b/src/components/Avatar/StackedAvatars.vue @@ -0,0 +1,5 @@ + diff --git a/src/components/Avatar/StackedAvatarsCounter.vue b/src/components/Avatar/StackedAvatarsCounter.vue new file mode 100644 index 0000000..521b367 --- /dev/null +++ b/src/components/Avatar/StackedAvatarsCounter.vue @@ -0,0 +1,15 @@ + + diff --git a/src/components/Avatar/composables/useAvatarClasses.ts b/src/components/Avatar/composables/useAvatarClasses.ts new file mode 100644 index 0000000..bfb5619 --- /dev/null +++ b/src/components/Avatar/composables/useAvatarClasses.ts @@ -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 = { + 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 = { + 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 = { + away: 'bg-gray-400', + busy: 'bg-yellow-400', + offline: 'bg-red-400', + online: 'bg-green-400', +} +const avatarStatusDotPositionClasses: Record = { + '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 + bordered: Ref + img: Ref + alt: Ref + rounded: Ref + size: Ref + stacked: Ref + statusPosition: Ref +} + +export function useAvatarClasses(props: UseAvatarClassesProps): { + avatarClasses: Ref + avatarDotClasses: Ref + avatarPlaceholderClasses: Ref + avatarPlaceholderWrapperClasses: Ref + avatarPlaceholderInitialsClasses: Ref +} { + + const avatarClasses = computed(() => { + 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(() => { + const avatarType = `${props.statusPosition.value}-${props.rounded.value ? 'rounded' : 'default'}` + return classNames( + avatarStatusDotDefaultClasses, + avatarStatusDotClasses[props.status.value], + avatarStatusDotPositionClasses[avatarType as avatarDotIndicatorPositionClasses], + ) + }) + const avatarPlaceholderClasses = computed(() => { + return classNames( + avatarPlaceholderDefaultClasses, + avatarPlaceholderSizes[props.size.value], + ) + }) + const avatarPlaceholderWrapperClasses = computed(() => { + return classNames( + avatarPlaceholderWrapperDefaultClasses, + avatarSizeClasses[props.size.value], + avatarTypeClasses[props.rounded.value ? 'rounded' : 'default'], + ) + }) + const avatarPlaceholderInitialsClasses = computed(() => { + return classNames( + avatarPlaceholderInitialsDefaultClasses, + ) + }) + // TODO: Avatar Initials + + return { + avatarClasses, + avatarDotClasses, + avatarPlaceholderClasses, + avatarPlaceholderWrapperClasses, + avatarPlaceholderInitialsClasses, + } +} + + diff --git a/src/components/Avatar/types.ts b/src/components/Avatar/types.ts new file mode 100644 index 0000000..d9903d0 --- /dev/null +++ b/src/components/Avatar/types.ts @@ -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}` diff --git a/src/index.ts b/src/index.ts index 64d1910..34a6643 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'