Skip to content

Commit

Permalink
feat: avatar base component
Browse files Browse the repository at this point in the history
  • Loading branch information
GODrums committed Aug 6, 2024
1 parent 251e366 commit 1c699cf
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
13 changes: 13 additions & 0 deletions webapp/src/app/ui/avatar/avatar.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div [class]="computedClass()">
@if (canShow()) {
<img
[src]="src()"
[alt]="alt()"
[class]="computedImageClass()"
placeholder
(error)="onError()"
/>
} @else {
<img [class]="computedImageClass()" src="https://placehold.co/64" />
}
</div>
90 changes: 90 additions & 0 deletions webapp/src/app/ui/avatar/avatar.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
ChangeDetectionStrategy,
Component,
computed,
input,
signal,
} from '@angular/core';
import type { ClassValue } from 'clsx';
import type { VariantProps } from 'class-variance-authority';
import { cn } from 'app/utils';
import { cva } from 'app/storybook.helper';
import { NgOptimizedImage } from '@angular/common';

const [avatarVariants, args, argTypes] = cva(
'relative flex shrink-0 overflow-hidden rounded-full',
{
variants: {
variant: {
small: 'h-6 w-6 text-xs',
medium: 'h-10 w-10',
large: 'h-14 w-14 text-lg',
},
},
defaultVariants: {
variant: 'medium',
},
},
);

export { args, argTypes };

interface AvatarVariants extends VariantProps<typeof avatarVariants> {}

@Component({
selector: 'app-avatar',
standalone: true,
imports: [NgOptimizedImage],
changeDetection: ChangeDetectionStrategy.OnPush,
// template: `
// <ng-container *ngIf="image?.canShow(); else fallback">
// <ng-content
// select="[appAvatarImage]"
// (error)="onError()"
// (load)="onLoad()"
// />
// </ng-container>
// <ng-template #fallback>
// <ng-content select="[avatarFallback]" />
// </ng-template>
// `,
templateUrl: './avatar.component.html',
})
// export class AppAvatarComponent {
// class = input<ClassValue>('');
// variant = input<AvatarVariants['variant']>('medium');

// @ContentChild(AvatarImageDirective, { static: true })
// image: AvatarImageDirective | null = null;

// computedClass = computed(() =>
// cn(avatarVariants({ variant: this.variant() }), this.class()),
// );
// }
export class AppAvatarComponent {
class = input<ClassValue>('');
variant = input<AvatarVariants['variant']>('medium');

src = input<string>('');
alt = input<string>('');
imageClass = input<string>('');
fallback = input<string>('');

canShow = signal(true);

onError = () => {
if (this.fallback.length > 0) {
this.src = this.fallback;
} else {
this.canShow.set(false);
}
};

computedClass = computed(() =>
cn(avatarVariants({ variant: this.variant() }), this.class()),
);

computedImageClass = computed(() =>
cn('aspect-square object-cover h-full w-full', this.imageClass()),
);
}
84 changes: 84 additions & 0 deletions webapp/src/app/ui/avatar/avatar.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { AppAvatarComponent, args, argTypes } from './avatar.component';

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
const meta: Meta<AppAvatarComponent> = {
title: 'UI/Avatar',
component: AppAvatarComponent,
tags: ['autodocs'],
args: {
...args,
},
argTypes: {
...argTypes,
},
};

export default meta;
type Story = StoryObj<AppAvatarComponent>;

// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Default: Story = {
args: {
variant: 'medium',
src: 'https://i.pravatar.cc/64?img=1',
alt: 'avatar',
class: '',
},

render: (args) => ({
props: args,
template: `<app-avatar ${argsToTemplate(args)}></app-avatar>`,
}),
};

export const Small: Story = {
args: {
variant: 'small',
src: 'https://placehold.co/24',
},

render: (args) => ({
props: args,
template: `<app-avatar ${argsToTemplate(args)}></app-avatar>`,
}),
};

export const Medium: Story = {
args: {
variant: 'medium',
src: 'https://placehold.co/40',
},

render: (args) => ({
props: args,
template: `<app-avatar ${argsToTemplate(args)}>MD</app-avatar>`,
}),
};

export const Large: Story = {
args: {
variant: 'large',
src: 'https://placehold.co/56',
},

render: (args) => ({
props: args,
template: `<app-avatar ${argsToTemplate(args)}>LG</app-avatar>`,
}),
};

export const WithImage: Story = {
args: {
variant: 'medium',
src: 'https://i.pravatar.cc/40',
},

render: (args) => ({
props: {
variant: 'outline',
size: 'icon',
},
template: `<app-avatar ${argsToTemplate(args)}></app-avatar>`,
}),
};

0 comments on commit 1c699cf

Please sign in to comment.