Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions apps/example/e2e/avatar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { expect, test } from '@playwright/test';

test.describe('avatars', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/avatars');
});

test('renders image avatar', async ({ page }) => {
await expect(page.locator('h2[data-id=avatars]')).toContainText('Avatars');

const avatar = page.getByTestId('avatar-image-lg');
await expect(avatar).toBeVisible();
await expect(avatar.locator('img')).toHaveAttribute('src', /.+/);
});

test('renders initials fallback at each size', async ({ page }) => {
for (const size of ['xs', 'sm', 'md', 'lg', 'xl', '2xl']) {
const avatar = page.getByTestId(`avatar-initials-${size}`);
await expect(avatar).toBeVisible();
await expect(avatar).toContainText('AL');
}
});

test('renders icon fallback', async ({ page }) => {
const avatar = page.getByTestId('avatar-icon-md');
await expect(avatar).toBeVisible();
await expect(avatar.locator('svg')).toBeVisible();
});

test('broken image falls back to initials', async ({ page }) => {
const avatar = page.getByTestId('avatar-broken');
await expect(avatar).toBeVisible();
await expect(avatar).toContainText('GH');
await expect(avatar.locator('img')).toHaveCount(0);
});

test('applies variant and color data attributes', async ({ page }) => {
await expect(page.getByTestId('avatar-variant-circular')).toHaveAttribute('data-variant', 'circular');
await expect(page.getByTestId('avatar-variant-rounded')).toHaveAttribute('data-variant', 'rounded');
await expect(page.getByTestId('avatar-variant-square')).toHaveAttribute('data-variant', 'square');
await expect(page.getByTestId('avatar-color-primary')).toHaveAttribute('data-color', 'primary');
await expect(page.getByTestId('avatar-color-success')).toHaveAttribute('data-color', 'success');
});

test('connection indicator dot renders via RuiBadge wrapper', async ({ page }) => {
const online = page.getByTestId('avatar-online');
await expect(online).toBeVisible();
// The RuiBadge dot is rendered as a sibling status element within the wrapper.
const wrapper = online.locator('xpath=..');
await expect(wrapper.locator('[role=status][data-dot=true]')).toBeVisible();
});

test('group renders visible avatars plus surplus', async ({ page }) => {
const group = page.getByTestId('avatar-group-max');
await expect(group).toBeVisible();
const surplus = group.getByTestId('avatar-group-surplus');
await expect(surplus).toBeVisible();
await expect(surplus).toContainText('+2');
});

test('group with total prop overrides surplus', async ({ page }) => {
const group = page.getByTestId('avatar-group-total');
const surplus = group.getByTestId('avatar-group-surplus');
await expect(surplus).toContainText('+39');
});

test('group injects size into children', async ({ page }) => {
const group = page.getByTestId('avatar-group-max');
const firstChild = group.locator('[data-id=avatar-root]').first();
await expect(firstChild).toHaveAttribute('data-size', 'lg');
});

test('avatar root is not focusable by default', async ({ page }) => {
const avatar = page.getByTestId('avatar-initials-md');
const tabindex = await avatar.getAttribute('tabindex');
expect(tabindex).toBeNull();
});
});
1 change: 1 addition & 0 deletions apps/example/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const navigation = ref([
{ to: '/dividers', title: 'Dividers' },
{ to: '/cards', title: 'Cards' },
{ to: '/tabs', title: 'Tabs' },
{ to: '/avatars', title: 'Avatars' },
{ to: '/badges', title: 'Badges' },
{ to: '/accordions', title: 'Accordions' },
{ to: '/dialogs', title: 'Dialogs' },
Expand Down
7 changes: 7 additions & 0 deletions apps/example/src/pages/avatars.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts" setup>
import AvatarView from '@/views/AvatarView.vue';
</script>

<template>
<AvatarView />
</template>
13 changes: 13 additions & 0 deletions apps/example/src/route-map.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ declare module 'vue-router/auto-routes' {
Record<never, never>,
| never
>,
'/avatars': RouteRecordInfo<
'/avatars',
'/avatars',
Record<never, never>,
Record<never, never>,
| never
>,
'/badges': RouteRecordInfo<
'/badges',
'/badges',
Expand Down Expand Up @@ -477,6 +484,12 @@ declare module 'vue-router/auto-routes' {
views:
| never
}
'src/pages/avatars.vue': {
routes:
| '/avatars'
views:
| never
}
'src/pages/badges.vue': {
routes:
| '/badges'
Expand Down
234 changes: 234 additions & 0 deletions apps/example/src/views/AvatarView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
<script lang="ts" setup>
import {
RuiAvatar,
RuiAvatarGroup,
RuiBadge,
} from '@rotki/ui-library/components';
import ComponentView from '@/components/ComponentView.vue';

const sizes = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const;
const variants = ['circular', 'rounded', 'square'] as const;
const colors = ['default', 'primary', 'secondary', 'error', 'warning', 'info', 'success'] as const;
</script>

<template>
<ComponentView data-id="avatars">
<template #title>
Avatars
</template>

<div class="space-y-10">
<section>
<h3 class="text-h6 mb-4">
Image
</h3>
<div class="flex items-end gap-6">
<RuiAvatar
v-for="s in sizes"
:key="s"
:size="s"
:data-id="`avatar-image-${s}`"
src="https://avatars.githubusercontent.com/u/3921489"
alt="Avatar image"
/>
</div>
</section>

<section>
<h3 class="text-h6 mb-4">
Initials fallback
</h3>
<div class="flex items-end gap-6">
<RuiAvatar
v-for="s in sizes"
:key="s"
:size="s"
:data-id="`avatar-initials-${s}`"
text="Ada Lovelace"
alt="Ada Lovelace"
/>
</div>
</section>

<section>
<h3 class="text-h6 mb-4">
Icon fallback
</h3>
<div class="flex items-end gap-6">
<RuiAvatar
v-for="s in sizes"
:key="s"
:size="s"
:data-id="`avatar-icon-${s}`"
icon="lu-user"
alt="User"
/>
</div>
</section>

<section>
<h3 class="text-h6 mb-4">
Broken image falls back to initials
</h3>
<RuiAvatar
data-id="avatar-broken"
src="https://example.invalid/missing.png"
text="Grace Hopper"
alt="Grace Hopper"
size="lg"
/>
</section>

<section>
<h3 class="text-h6 mb-4">
Variants
</h3>
<div class="flex items-center gap-6">
<RuiAvatar
v-for="v in variants"
:key="v"
:variant="v"
:data-id="`avatar-variant-${v}`"
text="AL"
size="lg"
/>
</div>
</section>

<section>
<h3 class="text-h6 mb-4">
Colors
</h3>
<div class="flex flex-wrap items-center gap-4">
<RuiAvatar
v-for="c in colors"
:key="c"
:color="c"
:data-id="`avatar-color-${c}`"
text="AL"
/>
</div>
</section>

<section>
<h3 class="text-h6 mb-4">
With connection indicator (wrapped in RuiBadge)
</h3>
<div class="flex items-center gap-8">
<RuiBadge
dot
color="success"
placement="bottom"
:offset-x="-2"
:offset-y="-2"
>
<RuiAvatar
text="AL"
size="lg"
data-id="avatar-online"
/>
</RuiBadge>
<RuiBadge
dot
color="warning"
placement="bottom"
:offset-x="-2"
:offset-y="-2"
>
<RuiAvatar
text="JS"
size="lg"
color="primary"
data-id="avatar-away"
/>
</RuiBadge>
<RuiBadge
dot
color="error"
placement="bottom"
:offset-x="-2"
:offset-y="-2"
>
<RuiAvatar
icon="lu-user"
size="lg"
color="secondary"
data-id="avatar-offline"
/>
</RuiBadge>
</div>
</section>

<section>
<h3 class="text-h6 mb-4">
Group
</h3>
<RuiAvatarGroup data-id="avatar-group-basic">
<RuiAvatar text="Ada Lovelace" />
<RuiAvatar
text="Grace Hopper"
color="primary"
/>
<RuiAvatar
text="Linus Torvalds"
color="secondary"
/>
</RuiAvatarGroup>
</section>

<section>
<h3 class="text-h6 mb-4">
Group with max (+surplus)
</h3>
<RuiAvatarGroup
:max="3"
size="lg"
data-id="avatar-group-max"
>
<RuiAvatar text="Ada Lovelace" />
<RuiAvatar
text="Grace Hopper"
color="primary"
/>
<RuiAvatar
text="Linus Torvalds"
color="secondary"
/>
<RuiAvatar
text="Margaret Hamilton"
color="success"
/>
<RuiAvatar
text="Donald Knuth"
color="warning"
/>
</RuiAvatarGroup>
</section>

<section>
<h3 class="text-h6 mb-4">
Group with total override
</h3>
<RuiAvatarGroup
:max="3"
:total="42"
data-id="avatar-group-total"
>
<RuiAvatar text="Ada" />
<RuiAvatar
text="Grace"
color="primary"
/>
<RuiAvatar
text="Linus"
color="secondary"
/>
<RuiAvatar
text="Margaret"
color="success"
/>
</RuiAvatarGroup>
</section>
</div>
</ComponentView>
</template>
Loading
Loading