avatar component

asd
This commit is contained in:
Benjamin Palko 2025-02-14 14:35:30 -05:00
parent 40bf33a9e5
commit f374c3aa4d
5 changed files with 146 additions and 18 deletions

View file

@ -0,0 +1,56 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import type { ComponentProps } from 'svelte';
import Avatar from './Avatar.svelte';
const { Story } = defineMeta({
title: 'Data display/Avatar',
component: Avatar,
argTypes: {
img: {
control: 'text',
},
placeholder: {
control: 'text',
},
presence: {
control: 'select',
options: [undefined, 'online', 'offline'],
},
ring: {
control: 'select',
options: [
undefined,
'neutral',
'primary',
'secondary',
'accent',
'info',
'success',
'warning',
'error',
],
},
shape: {
control: 'select',
options: ['square', 'circle'],
},
size: {
control: 'select',
options: ['xs', 'sm', 'md', 'lg'],
},
},
});
</script>
{#snippet template(props: ComponentProps<typeof Avatar>)}
<Avatar {...props} />
{/snippet}
<Story
name="Default"
args={{
img: 'https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp',
}}
children={template}
/>

View file

@ -0,0 +1,62 @@
<script lang="ts">
import type { DaisyColor, DaisySize } from '$lib/types';
import clsx from 'clsx';
import type { SvelteHTMLElements } from 'svelte/elements';
import { twMerge } from 'tailwind-merge';
type Props = {
img?: string;
placeholder: string;
presence?: 'online' | 'offline';
ring?: Exclude<DaisyColor, 'ghost'>;
shape?: 'square' | 'circle';
size?: DaisySize | 'md';
} & Omit<SvelteHTMLElements['div'], 'children'>;
let {
class: className,
img,
placeholder,
presence,
ring,
shape = 'circle',
size = 'md',
...props
}: Props = $props();
</script>
<div {...props} class={twMerge('avatar', presence, clsx(className))} class:placeholder>
<div
class={twMerge(
'rounded-xl',
clsx({
'rounded-xl': shape === 'square',
'rounded-full': shape === 'circle',
'w-12': size === 'xs',
'w-16': size === 'sm',
'w-20': size === 'md',
'w-32': size === 'lg',
'avatar-ring ring ring-offset-2 ring-offset-base-100': !!ring,
})
)}
class:bg-neutral={placeholder}
class:ring-neutral={ring === 'neutral'}
class:ring-primary={ring === 'primary'}
class:ring-secondary={ring === 'secondary'}
class:ring-accent={ring === 'accent'}
class:ring-info={ring === 'info'}
class:ring-success={ring === 'success'}
class:ring-warning={ring === 'warning'}
class:ring-error={ring === 'error'}
>
{#if img}
<img src={img} alt={placeholder} />
{:else}
<span
class:text-3xl={size === 'lg'}
class:text-xl={size === 'md'}
class:text-xs={size === 'xs'}>{placeholder}</span
>
{/if}
</div>
</div>

View file

@ -0,0 +1 @@
export { default as Avatar } from './Avatar.svelte';

View file

@ -5,17 +5,15 @@
import { messages } from '$lib/i18n'; import { messages } from '$lib/i18n';
import 'clerk-sveltekit/client'; import 'clerk-sveltekit/client';
import SignOutButton from 'clerk-sveltekit/client/SignOutButton.svelte'; import SignOutButton from 'clerk-sveltekit/client/SignOutButton.svelte';
import { Cog, LogOut, MessageCircleMore, UsersRound } from 'lucide-svelte'; import { Cog, LogOut, Menu, MessageCircleMore, UsersRound } from 'lucide-svelte';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { LayoutData } from './$types';
type Props = { type Props = {
children: Snippet; children: Snippet;
data: LayoutData;
}; };
let { children, data }: Props = $props(); let { children }: Props = $props();
let clerk; let clerk;
@ -45,22 +43,17 @@
} }
} }
}); });
let message = $derived(messages.nav_greeting({ name: data.user.name }));
</script> </script>
{#snippet userMenu()} {#snippet userMenu()}
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-circle btn-primary btn-sm ring"> <div
<div class="avatar placeholder online"> tabindex={0}
<div class="w-8 rounded-full"> role="button"
{#if data.user.hasImage} class="btn btn-primary btn-md flex items-center gap-2 text-lg"
<img src={data.user.imageUrl} alt="Avatar" /> >
{:else} <Menu />
<span>{data.user.name.at(0)}</span> Menu
{/if}
</div>
</div>
</div> </div>
<!-- svelte-ignore a11y_no_noninteractive_tabindex --> <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<ul <ul
@ -106,8 +99,7 @@
<h1 class="prose prose-2xl">Svelte</h1> <h1 class="prose prose-2xl">Svelte</h1>
{/snippet} {/snippet}
{#snippet end()} {#snippet end()}
<div class="flex items-center gap-3"> <div class="flex items-center gap-4 pr-2">
<p class="prose prose-lg">{message}</p>
{@render userMenu()} {@render userMenu()}
</div> </div>
{/snippet} {/snippet}

View file

@ -1,2 +1,19 @@
<script lang="ts"> <script lang="ts">
import { Avatar } from '$lib/components/Datadisplay';
import { messages } from '$lib/i18n';
import type { PageData } from './$types';
type Props = {
data: PageData;
};
let { data }: Props = $props();
let message = $derived(messages.nav_greeting({ name: data.user.name }));
let initials = $derived(data.user.name.split(' ').reduce((col, name) => col + name.at(0), ''));
</script> </script>
<div class="flex flex-col items-center gap-2">
<Avatar img={data.user.imageUrl} placeholder={initials} size="sm" ring="primary" />
<h1 class="prose prose-2xl">{message}</h1>
</div>