Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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">[email protected]</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;