Skip to content

Commit

Permalink
Merge pull request #18 from mouracamila/avatar-component
Browse files Browse the repository at this point in the history
Avatar component
  • Loading branch information
bacali95 authored Apr 13, 2022
2 parents 7396b27 + 432d9f5 commit 1790b4f
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 11 deletions.
9 changes: 9 additions & 0 deletions src/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
HiMenuAlt1,
HiPencilAlt,
HiStar,
HiUser,
} from 'react-icons/hi';
import { BsCreditCard2FrontFill, BsGithub, BsImages } from 'react-icons/bs';
import { FaSpinner } from 'react-icons/fa';
Expand All @@ -24,6 +25,7 @@ import { Route, Routes } from 'react-router-dom';
import { DarkThemeToggle, Navbar, Sidebar, SidebarItem, Spinner } from './components';

import DashboardPage from './pages/DashboardPage';
import AvatarPage from './pages/AvatarPage';
import AlertsPage from './pages/AlertsPage';
import AccordionPage from './pages/AccordionPage';
import BadgesPage from './pages/BadgesPage';
Expand Down Expand Up @@ -63,6 +65,12 @@ export const Root: FC = () => {
title: 'Accordion',
href: '/accordion',
},
{
group: false,
icon: HiUser,
title: 'Avatar',
href: '/avatar',
},
{
group: false,
icon: HiBadgeCheck,
Expand Down Expand Up @@ -196,6 +204,7 @@ export const Root: FC = () => {
<Route path="" element={<DashboardPage />} />
<Route path="alerts" element={<AlertsPage />} />
<Route path="accordion" element={<AccordionPage />} />
<Route path="avatar" element={<AvatarPage />} />
<Route path="badges" element={<BadgesPage />} />
<Route path="breadcrumb" element={<BreadcrumbPage />} />
<Route path="buttons" element={<ButtonsPage />} />
Expand Down
92 changes: 92 additions & 0 deletions src/components/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import classNames from 'classnames';
import React, { PropsWithChildren } from 'react';

export type AvatarProps = PropsWithChildren<{
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
rounded?: boolean;
bordered?: boolean;
img?: string;
status?: 'offline' | 'online' | 'away' | 'busy';
statusPosition?: 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left';
}>;

const sizeClasses: Record<AvatarProps['size'] & string, 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 statusClasses: Record<AvatarProps['status'] & string, string> = {
offline: 'bg-gray-400',
online: 'bg-green-400',
away: 'bg-yellow-400',
busy: 'bg-red-400',
};

const statusPositionClasses: Record<AvatarProps['statusPosition'] & string, string> = {
'top-left': '-top-1 -right-1',
'top-right': '-top-1 -left-1',
'bottom-left': '-bottom-1 -right-1',
'bottom-right': '-bottom-1 -left-1',
};

export const Avatar: React.FC<AvatarProps> = ({
img,
status,
children,
statusPosition = 'top-right',
size = 'md',
rounded = false,
bordered = false,
}) => {
return (
<div className="flex items-center space-x-4">
<div className="relative">
{img ? (
<img
className={classNames(sizeClasses[size], {
rounded: !rounded,
'rounded-full': rounded,
'p-1 ring-2 ring-gray-300 dark:ring-gray-500': bordered,
})}
src={img}
alt="Rounded avatar"
/>
) : (
<div
className={classNames(`relative overflow-hidden bg-gray-100 dark:bg-gray-600`, sizeClasses[size], {
rounded: !rounded,
'rounded-full': rounded,
'p-1 ring-2 ring-gray-300 dark:ring-gray-500': bordered,
})}
>
<svg
className="absolute -bottom-1 h-auto w-auto text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clipRule="evenodd"
></path>
</svg>
</div>
)}
{status && (
<span
className={classNames(
'absolute h-3.5 w-3.5 rounded-full border-2 border-white dark:border-gray-800',
statusClasses[status],
statusPositionClasses[statusPosition],
)}
></span>
)}
</div>
{children && <div>{children}</div>}
</div>
);
};
3 changes: 2 additions & 1 deletion src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentProps, FC } from 'react';
import { ComponentProps, FC, ReactNode } from 'react';
import classNames from 'classnames';

type Color = 'blue' | 'alternative' | 'dark' | 'light' | 'green' | 'red' | 'yellow' | 'purple';
Expand All @@ -17,6 +17,7 @@ type PositionInGroup = 'start' | 'middle' | 'end';
export type ButtonProps = Omit<ComponentProps<'button'>, 'color'> & {
pill?: boolean;
outline?: boolean;
label?: ReactNode;
color?: Color;
size?: Size;
icon?: FC<ComponentProps<'svg'>>;
Expand Down
17 changes: 7 additions & 10 deletions src/components/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { DropdownDivider } from './DropdownDivider';
import { DropdownHeader } from './DropdownHeader';

export type DropdownProps = ButtonProps &
Omit<TooltipProps, 'content' | 'style' | 'animation'> & {
Omit<TooltipProps, 'content' | 'style' | 'animation' | 'arrow'> & {
className?: string;
label: ReactNode;
inline?: boolean;
tooltipArrow?: boolean;
arrowIcon?: boolean;
};

const icons: Record<string, FC<ComponentProps<'svg'>>> = {
Expand All @@ -23,13 +25,8 @@ const icons: Record<string, FC<ComponentProps<'svg'>>> = {
};

const DropdownComponent: FC<DropdownProps> = (props) => {
const { children, className, label, inline, ...restProps } = props;
const {
placement = inline ? 'bottom-start' : 'bottom',
arrow = false,
trigger = 'click',
...buttonProps
} = restProps;
const { children, className, label, inline, tooltipArrow, arrowIcon = true, ...restProps } = props;
const { placement = inline ? 'bottom-start' : 'bottom', trigger = 'click', ...buttonProps } = restProps;

const Icon = useMemo(() => {
const [p] = placement.split('-');
Expand All @@ -48,12 +45,12 @@ const DropdownComponent: FC<DropdownProps> = (props) => {
style="auto"
animation="duration-100"
placement={placement}
arrow={arrow}
arrow={tooltipArrow}
trigger={trigger}
>
<TriggerWrapper>
{label}
<Icon className="ml-2 h-4 w-4" />
{arrowIcon && <Icon className="ml-2 h-4 w-4" />}
</TriggerWrapper>
</Tooltip>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './Alert';
export * from './accordion/Accordion';
export * from './Avatar';
export * from './Badge';
export * from './Breadcrumb';
export * from './Button';
Expand Down
108 changes: 108 additions & 0 deletions src/pages/AvatarPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { FC } from 'react';

import { Avatar, Dropdown } from '../components';
import { CodeExample, DemoPage } from './DemoPage';

const AvatarPage: FC = () => {
const examples: CodeExample[] = [
{
title: 'Default Avatar',
code: (
<div className="flex flex-wrap gap-2">
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" rounded />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" />
</div>
),
},
{
title: 'Bordered Avatar',
code: (
<div className="flex flex-wrap gap-2">
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" rounded bordered />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" bordered />
</div>
),
},
{
title: 'Placeholder',
code: (
<div className="flex flex-wrap gap-2">
<Avatar />
<Avatar rounded />
</div>
),
},
{
title: 'Dot indicator',
code: (
<div className="flex flex-wrap gap-2">
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" status="online" />
<Avatar
img="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
rounded
status="busy"
statusPosition="top-left"
/>
<Avatar
img="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
status="offline"
statusPosition="bottom-right"
/>
<Avatar
img="https://flowbite.com/docs/images/people/profile-picture-5.jpg"
rounded
status="away"
statusPosition="bottom-left"
/>
</div>
),
},
{
title: 'Avatar text',
code: (
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" rounded>
<div className="space-y-1 font-medium dark:text-white">
<div>Jese Leos</div>
<div className="text-sm text-gray-500 dark:text-gray-400">Joined in August 2014</div>
</div>
</Avatar>
),
},
{
title: 'User dropdown',
code: (
<Dropdown
label={<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" rounded />}
arrowIcon={false}
inline
>
<Dropdown.Header>
<span className="block text-sm">Bonnie Green</span>
<span className="block truncate text-sm font-medium">name@flowbite.com</span>
</Dropdown.Header>
<Dropdown.Item>Dashboard</Dropdown.Item>
<Dropdown.Item>Settings</Dropdown.Item>
<Dropdown.Item>Earnings</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item>Sign out</Dropdown.Item>
</Dropdown>
),
},
{
title: 'Sizing',
code: (
<div className="flex flex-wrap items-center gap-2">
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" size="xs" />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" size="sm" />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" size="md" />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" size="lg" />
<Avatar img="https://flowbite.com/docs/images/people/profile-picture-5.jpg" size="xl" />
</div>
),
},
];

return <DemoPage examples={examples} />;
};

export default AvatarPage;

0 comments on commit 1790b4f

Please sign in to comment.