Skip to content

Commit

Permalink
Merge pull request #147 from awell-health/avatar
Browse files Browse the repository at this point in the history
Add avatar component
  • Loading branch information
skrobek authored Nov 6, 2024
2 parents 01b66c7 + 08bb2db commit 275b8b9
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@awell-health/design-system",
"version": "0.11.6",
"version": "0.11.7",
"type": "module",
"files": [
"dist"
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './ui/alert';
export * from './ui/avatar';
export * from './ui/alert-dialog';
export * from './ui/badge';
export * from './ui/button';
Expand Down
35 changes: 35 additions & 0 deletions src/components/ui/avatar/__snapshots__/avatar.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Avatar > match image snapshot 1`] = `
<div>
<div
class="avatar"
>
<div
class="rounded-full w-16"
>
<img
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80""
/>
</div>
</div>
</div>
`;
exports[`Avatar > match placeholder snapshot 1`] = `
<div>
<div
class="avatar placeholder"
>
<div
class="bg-neutral text-neutral-content rounded-full w-16"
>
<span
class=""
>
SV
</span>
</div>
</div>
</div>
`;
21 changes: 21 additions & 0 deletions src/components/ui/avatar/avatar.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest';

import { Avatar } from './avatar';
import React from 'react';
import { render } from '@testing-library/react';

describe('Avatar', () => {
it('match image snapshot', () => {
const { container } = render(
<Avatar src='https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"' />
);

expect(container).toMatchSnapshot();
});

it('match placeholder snapshot', () => {
const { container } = render(<Avatar placeholder text='SV' />);

expect(container).toMatchSnapshot();
});
});
74 changes: 74 additions & 0 deletions src/components/ui/avatar/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';

import { cn } from '@/lib/utils';

const avatarVariants = cva('', {
variants: {
variant: {
circle: 'rounded-full',
square: 'rounded-md'
},
size: {
xs: 'w-8',
sm: 'w-16',
md: 'w-20',
lg: 'w-32'
}
},
defaultVariants: {
variant: 'circle',
size: 'sm'
}
});

export interface AvatarProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof avatarVariants> {
src?: string;
placeholder?: boolean;
text?: string;
}

function Avatar({
src,
className,
variant,
size,
placeholder = false,
text,
...props
}: AvatarProps) {
return (
<div className={cn('avatar', { placeholder: placeholder })}>
{!placeholder && (
<div className={cn(avatarVariants({ variant, size }), className)} {...props}>
<img src={src} />
</div>
)}
{placeholder && (
<div
className={cn(
'bg-neutral text-neutral-content w-24 rounded-full',
avatarVariants({ size }),
className
)}
{...props}
>
<span
className={cn({
'text-xs': size === 'xs',
'text-sm': size === 'sm',
'text-xl': size === 'md',
'text-3xl': size === 'lg'
})}
>
{text}
</span>
</div>
)}
</div>
);
}

export { Avatar, avatarVariants };
1 change: 1 addition & 0 deletions src/components/ui/avatar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Avatar } from './avatar';
21 changes: 21 additions & 0 deletions src/stories/Avatar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Avatar } from '@/components/ui/avatar';
import type { Meta, StoryObj } from '@storybook/react';

const meta = {
component: Avatar
} satisfies Meta<typeof Avatar>;

export default meta;

type Story = StoryObj<typeof Avatar>;

export const Example = {
args: {
variant: 'circle',
size: 'sm',
text: 'AV',
src: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"',
placeholder: false
},
render: (args) => <Avatar {...args} />
} satisfies Story;
102 changes: 102 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -726,12 +726,36 @@ html {
}
}

.avatar {
position: relative;
display: inline-flex;
}

.avatar > div {
display: block;
aspect-ratio: 1 / 1;
overflow: hidden;
}

.avatar img {
height: 100%;
width: 100%;
-o-object-fit: cover;
object-fit: cover;
}

.avatar.placeholder > div {
display: flex;
align-items: center;
justify-content: center;
}

.avatar.\!placeholder > div {
display: flex !important;
align-items: center !important;
justify-content: center !important;
}

.badge {
display: inline-flex;
align-items: center;
Expand Down Expand Up @@ -1713,6 +1737,14 @@ input.tab:checked + .tab-content,
var(--togglehandleborder);
}

.avatar-group :where(.avatar) {
overflow: hidden;
border-radius: 9999px;
border-width: 4px;
--tw-border-opacity: 1;
border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity)));
}

.btm-nav > *:where(.active) {
border-top-width: 2px;
--tw-bg-opacity: 1;
Expand Down Expand Up @@ -3138,6 +3170,40 @@ input.tab:checked + .tab-content,
bottom: auto;
}

.avatar.online:before {
content: "";
position: absolute;
z-index: 10;
display: block;
border-radius: 9999px;
--tw-bg-opacity: 1;
background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
outline-style: solid;
outline-width: 2px;
outline-color: var(--fallback-b1,oklch(var(--b1)/1));
width: 15%;
height: 15%;
top: 7%;
right: 7%;
}

.avatar.offline:before {
content: "";
position: absolute;
z-index: 10;
display: block;
border-radius: 9999px;
--tw-bg-opacity: 1;
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
outline-style: solid;
outline-width: 2px;
outline-color: var(--fallback-b1,oklch(var(--b1)/1));
width: 15%;
height: 15%;
top: 7%;
right: 7%;
}

.join.join-vertical > :where(*:not(:first-child)) {
margin-left: 0px;
margin-right: 0px;
Expand Down Expand Up @@ -3529,6 +3595,22 @@ input.tab:checked + .tab-content,
width: 100vw;
}

.w-16 {
width: 4rem;
}

.w-20 {
width: 5rem;
}

.w-32 {
width: 8rem;
}

.w-24 {
width: 6rem;
}

.min-w-8 {
min-width: 2rem;
}
Expand Down Expand Up @@ -3932,6 +4014,11 @@ input.tab:checked + .tab-content,
background-color: rgb(254 252 232 / var(--tw-bg-opacity));
}

.bg-neutral {
--tw-bg-opacity: 1;
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
}

.bg-opacity-30 {
--tw-bg-opacity: 0.3;
}
Expand Down Expand Up @@ -4126,6 +4213,16 @@ input.tab:checked + .tab-content,
line-height: 1rem;
}

.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}

.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}

.font-medium {
font-weight: 500;
}
Expand Down Expand Up @@ -4276,6 +4373,11 @@ input.tab:checked + .tab-content,
color: rgb(133 77 14 / var(--tw-text-opacity));
}

.text-neutral-content {
--tw-text-opacity: 1;
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
}

.\!shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1) !important;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color) !important;
Expand Down

0 comments on commit 275b8b9

Please sign in to comment.