From 1c699cfa7dd8d4d1a5dda658f68b42acc14ed986 Mon Sep 17 00:00:00 2001 From: GODrums Date: Tue, 6 Aug 2024 18:48:41 +0200 Subject: [PATCH] feat: avatar base component --- .../src/app/ui/avatar/avatar.component.html | 13 +++ webapp/src/app/ui/avatar/avatar.component.ts | 90 +++++++++++++++++++ webapp/src/app/ui/avatar/avatar.stories.ts | 84 +++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 webapp/src/app/ui/avatar/avatar.component.html create mode 100644 webapp/src/app/ui/avatar/avatar.component.ts create mode 100644 webapp/src/app/ui/avatar/avatar.stories.ts diff --git a/webapp/src/app/ui/avatar/avatar.component.html b/webapp/src/app/ui/avatar/avatar.component.html new file mode 100644 index 00000000..dd14b9eb --- /dev/null +++ b/webapp/src/app/ui/avatar/avatar.component.html @@ -0,0 +1,13 @@ +
+ @if (canShow()) { + + } @else { + + } +
diff --git a/webapp/src/app/ui/avatar/avatar.component.ts b/webapp/src/app/ui/avatar/avatar.component.ts new file mode 100644 index 00000000..be78befb --- /dev/null +++ b/webapp/src/app/ui/avatar/avatar.component.ts @@ -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 {} + +@Component({ + selector: 'app-avatar', + standalone: true, + imports: [NgOptimizedImage], + changeDetection: ChangeDetectionStrategy.OnPush, + // template: ` + // + // + // + // + // + // + // `, + templateUrl: './avatar.component.html', +}) +// export class AppAvatarComponent { +// class = input(''); +// variant = input('medium'); + +// @ContentChild(AvatarImageDirective, { static: true }) +// image: AvatarImageDirective | null = null; + +// computedClass = computed(() => +// cn(avatarVariants({ variant: this.variant() }), this.class()), +// ); +// } +export class AppAvatarComponent { + class = input(''); + variant = input('medium'); + + src = input(''); + alt = input(''); + imageClass = input(''); + fallback = input(''); + + 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()), + ); +} diff --git a/webapp/src/app/ui/avatar/avatar.stories.ts b/webapp/src/app/ui/avatar/avatar.stories.ts new file mode 100644 index 00000000..ff504460 --- /dev/null +++ b/webapp/src/app/ui/avatar/avatar.stories.ts @@ -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 = { + title: 'UI/Avatar', + component: AppAvatarComponent, + tags: ['autodocs'], + args: { + ...args, + }, + argTypes: { + ...argTypes, + }, +}; + +export default meta; +type Story = StoryObj; + +// 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: ``, + }), +}; + +export const Small: Story = { + args: { + variant: 'small', + src: 'https://placehold.co/24', + }, + + render: (args) => ({ + props: args, + template: ``, + }), +}; + +export const Medium: Story = { + args: { + variant: 'medium', + src: 'https://placehold.co/40', + }, + + render: (args) => ({ + props: args, + template: `MD`, + }), +}; + +export const Large: Story = { + args: { + variant: 'large', + src: 'https://placehold.co/56', + }, + + render: (args) => ({ + props: args, + template: `LG`, + }), +}; + +export const WithImage: Story = { + args: { + variant: 'medium', + src: 'https://i.pravatar.cc/40', + }, + + render: (args) => ({ + props: { + variant: 'outline', + size: 'icon', + }, + template: ``, + }), +};