feat: avatar component (#76)
This commit is contained in:
parent
c8f1c58507
commit
f0121605c4
@ -6,6 +6,7 @@
|
||||
"@fontsource/inter": "^5.0.16",
|
||||
"@material-tailwind/react": "^2.1.7",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
@ -0,0 +1,71 @@
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const avatarTheme = tv(
|
||||
{
|
||||
base: ['relative', 'block', 'rounded-full', 'overflow-hidden'],
|
||||
slots: {
|
||||
image: [
|
||||
'h-full',
|
||||
'w-full',
|
||||
'rounded-[inherit]',
|
||||
'object-cover',
|
||||
'object-center',
|
||||
],
|
||||
fallback: [
|
||||
'grid',
|
||||
'select-none',
|
||||
'place-content-center',
|
||||
'h-full',
|
||||
'w-full',
|
||||
'rounded-[inherit]',
|
||||
'font-medium',
|
||||
],
|
||||
},
|
||||
variants: {
|
||||
type: {
|
||||
gray: {
|
||||
fallback: ['text-elements-highEm', 'bg-base-bg-emphasized'],
|
||||
},
|
||||
orange: {
|
||||
fallback: ['text-elements-warning', 'bg-base-bg-emphasized-warning'],
|
||||
},
|
||||
blue: {
|
||||
fallback: ['text-elements-info', 'bg-base-bg-emphasized-info'],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
18: {
|
||||
base: ['rounded-md', 'h-[18px]', 'w-[18px]', 'text-[0.625rem]'],
|
||||
},
|
||||
20: {
|
||||
base: ['rounded-md', 'h-5', 'w-5', 'text-[0.625rem]'],
|
||||
},
|
||||
24: {
|
||||
base: ['rounded-md', 'h-6', 'w-6', 'text-[0.625rem]'],
|
||||
},
|
||||
28: {
|
||||
base: ['rounded-lg', 'h-[28px]', 'w-[28px]', 'text-[0.625rem]'],
|
||||
},
|
||||
32: {
|
||||
base: ['rounded-lg', 'h-8', 'w-8', 'text-xs'],
|
||||
},
|
||||
36: {
|
||||
base: ['rounded-xl', 'h-[36px]', 'w-[36px]', 'text-xs'],
|
||||
},
|
||||
40: {
|
||||
base: ['rounded-xl', 'h-10', 'w-10', 'text-sm'],
|
||||
},
|
||||
44: {
|
||||
base: ['rounded-xl', 'h-[44px]', 'w-[44px]', 'text-sm'],
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 24,
|
||||
type: 'gray',
|
||||
},
|
||||
},
|
||||
{ responsiveVariants: true },
|
||||
);
|
||||
|
||||
export type AvatarVariants = VariantProps<typeof avatarTheme>;
|
40
packages/frontend/src/components/shared/Avatar/Avatar.tsx
Normal file
40
packages/frontend/src/components/shared/Avatar/Avatar.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { type ComponentPropsWithoutRef, type ComponentProps } from 'react';
|
||||
import { avatarTheme, type AvatarVariants } from './Avatar.theme';
|
||||
import * as PrimitiveAvatar from '@radix-ui/react-avatar';
|
||||
|
||||
export type AvatarProps = ComponentPropsWithoutRef<'div'> & {
|
||||
imageSrc?: string | null;
|
||||
initials?: string;
|
||||
imageProps?: ComponentProps<typeof PrimitiveAvatar.Image>;
|
||||
fallbackProps?: ComponentProps<typeof PrimitiveAvatar.Fallback>;
|
||||
} & AvatarVariants;
|
||||
|
||||
export const Avatar = ({
|
||||
className,
|
||||
size,
|
||||
type,
|
||||
imageSrc,
|
||||
imageProps,
|
||||
fallbackProps,
|
||||
initials,
|
||||
}: AvatarProps) => {
|
||||
const { base, image, fallback } = avatarTheme({ size, type });
|
||||
|
||||
return (
|
||||
<PrimitiveAvatar.Root className={base({ className })}>
|
||||
{imageSrc && (
|
||||
<PrimitiveAvatar.Image
|
||||
{...imageProps}
|
||||
className={image({ className: imageProps?.className })}
|
||||
src={imageSrc}
|
||||
/>
|
||||
)}
|
||||
<PrimitiveAvatar.Fallback asChild {...fallbackProps}>
|
||||
<div className={fallback({ className: fallbackProps?.className })}>
|
||||
{initials}
|
||||
</div>
|
||||
</PrimitiveAvatar.Fallback>
|
||||
</PrimitiveAvatar.Root>
|
||||
);
|
||||
};
|
2
packages/frontend/src/components/shared/Avatar/index.ts
Normal file
2
packages/frontend/src/components/shared/Avatar/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './Avatar';
|
||||
export * from './Avatar.theme';
|
@ -1,3 +1,4 @@
|
||||
import { Avatar, AvatarVariants } from 'components/shared/Avatar';
|
||||
import { Badge, BadgeProps } from 'components/shared/Badge';
|
||||
import { Button, ButtonOrLinkProps } from 'components/shared/Button';
|
||||
import { Calendar } from 'components/shared/Calendar';
|
||||
@ -6,10 +7,37 @@ import { PlusIcon } from 'components/shared/CustomIcon';
|
||||
import React, { useState } from 'react';
|
||||
import { Value } from 'react-calendar/dist/cjs/shared/types';
|
||||
|
||||
const avatarSizes: AvatarVariants['size'][] = [18, 20, 24, 28, 32, 36, 40, 44];
|
||||
const avatarVariants: AvatarVariants['type'][] = ['gray', 'orange', 'blue'];
|
||||
|
||||
const Page = () => {
|
||||
const [singleDate, setSingleDate] = useState<Value>();
|
||||
const [dateRange, setDateRange] = useState<Value>();
|
||||
|
||||
const avatars = avatarSizes.map((size) => {
|
||||
return (
|
||||
<Avatar
|
||||
initials="SY"
|
||||
key={String(size)}
|
||||
size={size}
|
||||
imageSrc="/gray.png"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const avatarsFallback = avatarVariants.map((color) => {
|
||||
return avatarSizes.map((size) => {
|
||||
return (
|
||||
<Avatar
|
||||
initials="SY"
|
||||
key={`${color}-${size}`}
|
||||
type={color}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative h-full min-h-full">
|
||||
<div className="flex flex-col items-center justify-center max-w-7xl mx-auto px-20 py-20">
|
||||
@ -76,82 +104,97 @@ const Page = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Badge</h1>
|
||||
<div className="space-y-5">
|
||||
{['primary', 'secondary', 'tertiary', 'inset'].map(
|
||||
(variant, index) => (
|
||||
<div className="flex gap-5" key={index}>
|
||||
{['sm', 'xs'].map((size) => (
|
||||
<Badge
|
||||
key={size}
|
||||
variant={variant as BadgeProps['variant']}
|
||||
size={size as BadgeProps['size']}
|
||||
>
|
||||
1
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Checkbox</h1>
|
||||
<div className="flex gap-10 flex-wrap">
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Checkbox
|
||||
id={`checkbox-${index + 1}`}
|
||||
key={index}
|
||||
label={`Label ${index + 1}`}
|
||||
disabled={index === 2 || index === 4 ? true : false}
|
||||
checked={index === 4 ? true : undefined}
|
||||
value={`value-${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-10 flex-wrap">
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Checkbox
|
||||
id={`checkbox-description-${index + 1}`}
|
||||
key={index}
|
||||
label={`Label ${index + 1}`}
|
||||
description={`Description of the checkbox ${index + 1}`}
|
||||
value={`value-with-description-${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Calendar</h1>
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="space-y-5 flex flex-col items-center">
|
||||
<p>Selected date: {singleDate?.toString()}</p>
|
||||
<Calendar
|
||||
value={singleDate}
|
||||
onChange={setSingleDate}
|
||||
onSelect={setSingleDate}
|
||||
/>
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Badge</h1>
|
||||
<div className="space-y-5">
|
||||
{['primary', 'secondary', 'tertiary', 'inset'].map(
|
||||
(variant, index) => (
|
||||
<div className="flex gap-5" key={index}>
|
||||
{['sm', 'xs'].map((size) => (
|
||||
<Badge
|
||||
key={size}
|
||||
variant={variant as BadgeProps['variant']}
|
||||
size={size as BadgeProps['size']}
|
||||
>
|
||||
1
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-5 flex flex-col items-center">
|
||||
<p>
|
||||
Start date:{' '}
|
||||
{dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '}
|
||||
<br />
|
||||
End date:{' '}
|
||||
{dateRange instanceof Array ? dateRange[1]?.toString() : ''}
|
||||
</p>
|
||||
<Calendar selectRange value={dateRange} onChange={setDateRange} />
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Checkbox</h1>
|
||||
<div className="flex gap-10 flex-wrap">
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<Checkbox
|
||||
id={`checkbox-${index + 1}`}
|
||||
key={index}
|
||||
label={`Label ${index + 1}`}
|
||||
disabled={index === 2 || index === 4 ? true : false}
|
||||
checked={index === 4 ? true : undefined}
|
||||
value={`value-${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-10 flex-wrap">
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Checkbox
|
||||
id={`checkbox-description-${index + 1}`}
|
||||
key={index}
|
||||
label={`Label ${index + 1}`}
|
||||
description={`Description of the checkbox ${index + 1}`}
|
||||
value={`value-with-description-${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Calendar</h1>
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="space-y-5 flex flex-col items-center">
|
||||
<p>Selected date: {singleDate?.toString()}</p>
|
||||
<Calendar
|
||||
value={singleDate}
|
||||
onChange={setSingleDate}
|
||||
onSelect={setSingleDate}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-5 flex flex-col items-center">
|
||||
<p>
|
||||
Start date:{' '}
|
||||
{dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '}
|
||||
<br />
|
||||
End date:{' '}
|
||||
{dateRange instanceof Array ? dateRange[1]?.toString() : ''}
|
||||
</p>
|
||||
<Calendar
|
||||
selectRange
|
||||
value={dateRange}
|
||||
onChange={setDateRange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
{/* Avatar */}
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Avatar</h1>
|
||||
<div className="flex gap-10 flex-wrap max-w-[522px]">
|
||||
{avatars}
|
||||
{avatarsFallback}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
57
yarn.lock
57
yarn.lock
@ -1280,7 +1280,7 @@
|
||||
resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz"
|
||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||
|
||||
"@babel/runtime@^7.10.4", "@babel/runtime@^7.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.3.1":
|
||||
"@babel/runtime@^7.10.4", "@babel/runtime@^7.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.3.1":
|
||||
version "7.23.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
||||
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
||||
@ -3277,6 +3277,61 @@
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||
|
||||
"@radix-ui/react-avatar@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623"
|
||||
integrity sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-compose-refs@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989"
|
||||
integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-context@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c"
|
||||
integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-primitive@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0"
|
||||
integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
|
||||
"@radix-ui/react-slot@1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"
|
||||
integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
|
||||
"@radix-ui/react-use-callback-ref@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"
|
||||
integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399"
|
||||
integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/primitive@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd"
|
||||
|
Loading…
Reference in New Issue
Block a user