Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avatar styles #175

Closed
wants to merge 13 commits into from
86 changes: 59 additions & 27 deletions src/elements/Avatar/Avatar.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,79 @@ export default {
title: 'Components/Elements/Avatar',
component: Avatar
} as Meta;
const ComponentsBlock = args => (
<div
className="bg-panel p-20"
style={{
display: 'flex',
gap: '1em',
flexDirection: 'column',
alignItems: 'start'
}}
>
<div
style={{
display: 'flex',
gap: '1em',
flexDirection: 'row',
justifyContent: 'start',
alignItems: 'center'
}}
>
<Avatar {...args} interactable tabIndex={1} />
<span>Active</span>
</div>
<div
style={{
display: 'flex',
gap: '1em',
flexDirection: 'row',
justifyContent: 'start',
alignItems: 'center'
}}
>
<Avatar {...args} disabled />
<span>Disabled</span>
</div>
</div>
);

const Template = args => <Avatar {...args} />;

export const Simple = Template.bind({});
Simple.args = {
export const Outline = args => <ComponentsBlock {...args} />;
Outline.args = {
name: 'John Doe',
size: 50,
rounded: false
variant: 'outline',
type: 'monochrome'
};

export const Outline = Template.bind({});
Outline.args = {
export const Filled = args => <ComponentsBlock {...args} />;
Filled.args = {
name: 'John Doe',
size: 50,
rounded: false,
variant: 'outline'
variant: 'filled',
type: 'monochrome'
};

export const RoundedWithImage = Template.bind({});
RoundedWithImage.args = {
src: 'https://goodcode.us/static/austin-d1a2c5249336c31662b8ee6d4e169b2b.jpg',
export const Colored = args => <ComponentsBlock {...args} />;
Colored.args = {
name: 'John Doe',
size: 50,
rounded: true
variant: 'filled',
type: 'colored'
};

export const LargeRounded = Template.bind({});
LargeRounded.args = {
name: 'John Doe',
size: 100,
rounded: true
export const Image = args => <ComponentsBlock {...args} />;
Image.args = {
src: 'https://goodcode.us/static/austin-d1a2c5249336c31662b8ee6d4e169b2b.jpg',
size: 50
};

export const MultipleAvatars = args => (
export const Sizes = args => (
<div style={{ display: 'flex', gap: '1em' }}>
<Avatar {...args} name="Alice" />
<Avatar {...args} name="Bob Meyer Bogger" />
<Avatar {...args} name="Charlie" onClick={() => console.log('here')} />
<Avatar {...args} size={50} />
<Avatar {...args} size={75} />
<Avatar {...args} size={100} />
</div>
);

MultipleAvatars.args = {
size: 50,
rounded: true
Sizes.args = {
src: 'https://goodcode.us/static/austin-d1a2c5249336c31662b8ee6d4e169b2b.jpg'
};
47 changes: 38 additions & 9 deletions src/elements/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { FC, LegacyRef, forwardRef, useMemo } from 'react';
import getInitials from 'name-initials';
import { generateColor } from '@marko19907/string-to-color';
import { twMerge } from 'tailwind-merge';
import { useComponentTheme } from '@/utils';
import { cn, useComponentTheme } from '@/utils';
import { AvatarTheme } from './AvatarTheme';

export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
Expand All @@ -26,6 +25,11 @@ export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
*/
variant?: 'filled' | 'outline';

/**
* Style type for the avatar.
*/
type?: 'colored' | 'monochrome';

/**
* Whether the avatar is rounded.
*/
Expand All @@ -36,6 +40,16 @@ export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
*/
color?: string;

/**
* Whether the avatar is disabled.
*/
disabled?: boolean;

/**
* Whether the avatar is able to interact.
*/
interactable?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be inverse of disable. No need for new prop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases we don't neet user interaction with this component

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is that different from disabled?


/**
* Custom color options for the color generator.
*/
Expand Down Expand Up @@ -69,9 +83,12 @@ export const Avatar: FC<AvatarProps> & AvatarRef = forwardRef<
color,
size = 24,
variant = 'filled',
type = 'colored',
rounded = true,
className,
colorOptions,
disabled,
interactable,
theme: customTheme,
...rest
},
Expand All @@ -94,22 +111,34 @@ export const Avatar: FC<AvatarProps> & AvatarRef = forwardRef<
}, [color, name, src, colorOptions]);

const theme: AvatarTheme = useComponentTheme('avatar', customTheme);
const themeVariant = src ? 'outline' : variant;

return (
<div
{...rest}
className={twMerge(theme.base, rounded && theme.rounded, className)}
className={cn(
theme.base,
theme[themeVariant].base,
theme[type].base,
{
'cursor-pointer': interactable,
[theme.rounded]: rounded,
[theme.disabled]: disabled,
[theme[themeVariant].focused]: interactable,
[theme[themeVariant].hovered]: interactable,
[theme[themeVariant].disabled]: disabled,
[theme[type].focused]: interactable,
[theme[type].hovered]: interactable,
[theme[type].disabled]: disabled
},
className
)}
style={{
width: `${size}px`,
height: `${size}px`,
fontSize: `${fontSize}px`,
backgroundImage: src ? `url(${src})` : 'none',
backgroundColor,
...(variant === 'outline' && {
backgroundColor: 'transparent',
border: `solid 1px ${backgroundColor}`,
color: backgroundColor
})
...(type === 'colored' && !disabled ? { backgroundColor } : {})
}}
ref={ref}
>
Expand Down
111 changes: 108 additions & 3 deletions src/elements/Avatar/AvatarTheme.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,121 @@
export interface AvatarTheme {
base: string;
rounded: string;
outline: {
base: string;
disabled: string;
focused: string;
hovered: string;
};
filled: {
base: string;
disabled: string;
focused: string;
hovered: string;
};
colored: {
base: string;
disabled: string;
focused: string;
hovered: string;
};
monochrome: {
base: string;
disabled: string;
focused: string;
hovered: string;
};
disabled: string;
}

const baseTheme: AvatarTheme = {
base: 'flex justify-center items-center bg-cover bg-center font-bold',
rounded: 'rounded-[50%]'
base: 'flex justify-center items-center bg-cover bg-center font-bold transition-[border-color] ease-in-out duration-300',
rounded: 'rounded-[50%]',
outline: {
base: 'border border-solid',
focused: '',
hovered: '',
disabled: ''
},
filled: {
base: 'border border-solid border-transparent',
focused: '',
hovered: '',
disabled: ''
},
colored: {
base: '',
focused: '',
hovered: '',
disabled: ''
},
monochrome: {
base: '',
focused: '',
hovered: '',
disabled: ''
},
disabled: 'border border-solid'
};

export const avatarTheme: AvatarTheme = {
...baseTheme,
base: [baseTheme.base, 'text-white'].join(' ')
base: [
baseTheme.base,
'text-waterloo text-gray-400 light:text-gray-600 focus:outline-none'
].join(' '),
outline: {
base: [
baseTheme.outline.base,
'bg-black border-gray-700 light:border-gray-200 light:bg-gray-100'
].join(' '),
hovered: [
baseTheme.outline.hovered,
'hover:border-primary-hover light:hover:border-primary-hover'
].join(' '),
focused: [
baseTheme.outline.focused,
'focus:border-primary-active light:focus:border-primary-active'
].join(' '),
disabled: [
baseTheme.outline.disabled,
'text-gray-300/40 light:bg-gray-100 light:border-gray-400'
].join(' ')
},
filled: {
base: [baseTheme.filled.base, 'bg-gray-700 light:bg-gray-200'].join(' '),
hovered: [
baseTheme.outline.hovered,
'hover:border-primary-hover light:hover:border-primary-hover'
].join(' '),
focused: [
baseTheme.outline.focused,
'focus:border-primary-active light:focus:border-primary-active'
].join(' '),
disabled: [
baseTheme.filled.disabled,
'bg-gray-600 light:bg-gray-100 light:border-gray-100 text-gray-300/40'
].join(' ')
},
colored: {
base: [baseTheme.colored.base, 'text-white light:text-white'].join(' '),
hovered: [
baseTheme.outline.hovered,
'hover:border-primary-hover light:hover:border-primary-hover'
].join(' '),
focused: [
baseTheme.outline.focused,
'focus:border-primary-active light:focus:border-primary-active'
].join(' '),
disabled: [
baseTheme.colored.disabled,
'bg-gray-600 light:bg-gray-100 light:border-gray-100 text-gray-300/40'
].join(' ')
},
disabled: [
baseTheme.disabled,
'cursor-not-allowed border-gray-600 light:border-gray-400 light:text-gray-400'
].join(' ')
};

export const legacyAvatarTheme: AvatarTheme = {
Expand Down
Loading