diff --git a/src/components/atoms/avatar/Avatar.stories.tsx b/src/components/atoms/avatar/Avatar.stories.tsx index df314db5..9e19b8b1 100644 --- a/src/components/atoms/avatar/Avatar.stories.tsx +++ b/src/components/atoms/avatar/Avatar.stories.tsx @@ -1,4 +1,3 @@ -import Icon from '@atoms/icon'; import type { Meta, StoryObj } from '@storybook/react'; import Avatar from './Avatar'; @@ -70,61 +69,18 @@ export const WithImage: Story = { }; /** - * - You can add a badge to the avatar by using the `hasBadge` prop. - * - The `badgeContent` prop allows you to customize the badge text. + * - You can round the avatar using the `rounded` prop. + * - The available options are 'md', 'full', and 'none'. + * - The default value is 'md'. + * - This prop allows for customization of the avatar's appearance. */ -export const WithBadge: Story = { +export const Rounded: Story = { render: () => (
- - - - } - badgeClassName='bg-red-500 text-white' - /> - } - badgeClassName='bg-blue-500 text-white' - /> - } - badgeClassName='bg-green-500 text-white' - /> + + +
) }; diff --git a/src/components/atoms/avatar/Avatar.tsx b/src/components/atoms/avatar/Avatar.tsx index b0584429..f4e4b77b 100644 --- a/src/components/atoms/avatar/Avatar.tsx +++ b/src/components/atoms/avatar/Avatar.tsx @@ -1,7 +1,8 @@ import { cn } from '@/lib/utils'; import * as AvatarPrimitive from '@radix-ui/react-avatar'; -import type { ComponentProps } from 'react'; +import type { ComponentProps, FC } from 'react'; import type { AvatarProps } from './types'; +import { useAvatar } from './useAvatar'; function AvatarContainer({ className, ...props }: ComponentProps) { return ( @@ -29,62 +30,25 @@ function AvatarFallback({ className, ...props }: ComponentProps { - const sizeClasses = { - sm: '30px', - md: '40px', - lg: '50px', - xl: '60px', - '2xl': '70px', - '3xl': '80px' - }; - const textClasses = { - sm: 'text-[0.8em]', - md: 'text-[1em]', - lg: 'text-[1.2em]', - xl: 'text-[1.4em]', - '2xl': 'text-[1.6em]', - '3xl': 'text-[1.8em]' - }; - const sizeClass = sizeClasses[size]; - const textClass = textClasses[size]; +const Avatar: FC = ({ ...props }) => { + const { src, alt, sizeClass, className, textClass, roundedClass } = useAvatar({ ...props }); return ( -
- - - - {alt} - - - {hasBadge && ( - - {badgeContent} - + + style={{ width: sizeClass, height: sizeClass }} + role='img' + aria-label={alt} + > + + + {alt} + + ); }; diff --git a/src/components/atoms/avatar/types.ts b/src/components/atoms/avatar/types.ts index 3936299e..ec51360c 100644 --- a/src/components/atoms/avatar/types.ts +++ b/src/components/atoms/avatar/types.ts @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react'; +type ThemeRounded = 'md' | 'full' | 'none'; export type AvatarProps = { /** @control src */ @@ -9,13 +9,6 @@ export type AvatarProps = { alt: string; /** @control text */ className?: string; - /** - * @control boolean - * @default false - */ - hasBadge?: boolean; - /** @control text */ - badgeContent?: string | ReactNode; - /** @control text */ - badgeClassName?: string; + /** @control select */ + rounded?: ThemeRounded; }; diff --git a/src/components/atoms/avatar/useAvatar.ts b/src/components/atoms/avatar/useAvatar.ts new file mode 100644 index 00000000..727dc2fe --- /dev/null +++ b/src/components/atoms/avatar/useAvatar.ts @@ -0,0 +1,33 @@ +import type { AvatarProps } from './types'; + +export const useAvatar = ({ src, alt = 'EG', className, size = 'md', rounded = 'md' }: AvatarProps) => { + const sizeClasses = { + sm: '30px', + md: '40px', + lg: '50px', + xl: '60px', + '2xl': '70px', + '3xl': '80px' + }; + const textClasses = { + sm: 'text-[0.8em]', + md: 'text-[1em]', + lg: 'text-[1.2em]', + xl: 'text-[1.4em]', + '2xl': 'text-[1.6em]', + '3xl': 'text-[1.8em]' + }; + + const roundedClass = rounded ? `rounded-${rounded}` : ''; + const sizeClass = sizeClasses[size]; + const textClass = textClasses[size]; + + return { + src, + alt, + className, + sizeClass, + textClass, + roundedClass + }; +}; diff --git a/src/components/atoms/dropdown/Dropdown.tsx b/src/components/atoms/dropdown/Dropdown.tsx index 0152b124..4e6b2f98 100644 --- a/src/components/atoms/dropdown/Dropdown.tsx +++ b/src/components/atoms/dropdown/Dropdown.tsx @@ -1,8 +1,10 @@ import { cn } from '@/lib/utils'; import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; import { ChevronRightIcon } from 'lucide-react'; +import type { FC } from 'react'; import { SpinnerCircular } from 'spinners-react'; import type { DropdownElement, DropdownProps } from './types'; +import { useDropdown } from './useDropdown'; const renderDropdownItem = (element: DropdownElement, index: number) => { if (element.type === 'item') { @@ -15,6 +17,7 @@ const renderDropdownItem = (element: DropdownElement, index: number) => { className={cn( 'relative flex cursor-default justify-between items-center gap-2 rounded-sm px-2 py-1.5 text-sm', 'transition-opacity duration-300 ease-in-out', + 'hover:outline-offset-1 dark:hover:outline-white hover:outline-secondary hover:outline-1', 'focus-visible:outline-offset-1 dark:focus-visible:outline-white focus-visible:outline-secondary focus-visible:outline-1', element.variant === 'destructive' && 'bg-secondary text-text-dark hover:bg-red-secondary-hover' )} @@ -40,6 +43,7 @@ const renderDropdownSubmenu = (element: DropdownElement, index: number) => { className={cn( 'flex items-center rounded-md justify-between px-2 py-1.5 text-sm', 'transition-opacity duration-300 ease-in-out', + 'hover:outline-offset-1 dark:hover:outline-white hover:outline-secondary hover:outline-1', 'focus-visible:outline-offset-1 dark:focus-visible:outline-white focus-visible:outline-secondary focus-visible:outline-1' )} > @@ -92,34 +96,26 @@ const renderDropdownElement = (element: DropdownElement, index: number) => { } }; -const Dropdown = ({ - width = '56px', - position = 'bottom', - align = 'center', - offset = 1, - closeOnSelect = true, - items, - loading = false, - children, - className -}: DropdownProps) => { - const marginClasses = { - top: 'mb-2', - bottom: 'mt-2', - left: 'mr-2', - right: 'ml-2' - }; - - const firstLabelId = items.find((item) => item.type === 'label')?.label - ? `dropdown-label-${items.findIndex((item) => item.type === 'label')}` - : undefined; - const fallbackId = 'dropdown-fallback-label'; - const accessibleLabelId = firstLabelId || fallbackId; +const Dropdown: FC = ({ ...props }) => { + const { + items, + loading, + closeOnSelect, + position, + align, + offset, + width, + className, + accessibleLabelId, + marginClasses, + firstLabelId, + fallbackId + } = useDropdown(props); return ( -
{children}
+
{props.children}
{ + const marginClasses = { + top: 'mb-2', + bottom: 'mt-2', + left: 'mr-2', + right: 'ml-2' + }; + + const firstLabelId = items.find((item) => item.type === 'label')?.label + ? `dropdown-label-${items.findIndex((item) => item.type === 'label')}` + : undefined; + const fallbackId = 'dropdown-fallback-label'; + const accessibleLabelId = firstLabelId || fallbackId; + + return { + items, + loading, + closeOnSelect, + position, + align, + offset, + width, + className, + accessibleLabelId, + marginClasses, + firstLabelId, + fallbackId + }; +}; diff --git a/src/components/atoms/icon-button/types.ts b/src/components/atoms/icon-button/types.ts index b17430ba..2266bb3d 100644 --- a/src/components/atoms/icon-button/types.ts +++ b/src/components/atoms/icon-button/types.ts @@ -3,7 +3,7 @@ import { type VariantProps, cva } from 'class-variance-authority'; export const iconButtonVariants = cva( [ - 'link relative overflow-hidden border-2 cursor-pointer px-2 py-2 max-w-full', + 'link relative overflow-hidden border-2 cursor-pointer px-1 py-1 max-w-full', 'transition-all duration-200 ease-in-out', 'flex items-center justify-start', 'whitespace-nowrap line-clamp-1 ',