Перейти к содержимому

Карточки

Отзыв

Карточка отзыва с аватаром, именем, ролью и текстом. Автоматически генерирует инициалы при отсутствии аватара.


Testimonial

Карточка отзыва с аватаром, именем, ролью и текстом. При отсутствии аватара автоматически генерирует инициалы из имени.

Интерактивное демо

Пропсы

<script lang="ts">
	import { cn } from '$lib/utils/cn';

	type Props = {
		avatar?: string;
		name: string;
		role?: string;
		content: string;
		class?: string;
	};

	let {
		avatar = '',
		name,
		role = '',
		content,
		class: className = ''
	}: Props = $props();

	const initials = $derived(
		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- name is typed as required, but callers outside this codebase's type-checking (JS, dynamic props, unchecked SSR data) can still pass undefined at runtime.
		(name ?? '')
			.split(' ')
			.map((n) => n[0])
			.slice(0, 2)
			.join('')
			.toUpperCase()
	);
</script>

<div
	class={cn(
		'inset-shadow rounded-md bg-background p-4',
		className
	)}
>
	<div class="flex items-start gap-3">
		{#if avatar}
			<img src={avatar} alt={name} class="size-10 rounded-full object-cover" />
		{:else}
			<div class="flex size-10 items-center justify-center rounded-full bg-accent/10 text-sm font-medium text-accent">
				{initials}
			</div>
		{/if}
		<div class="flex-1">
			<div class="flex items-center gap-2">
				<span class="text-sm font-medium text-foreground">{name}</span>
				{#if role}
					<span class="text-xs text-foreground-muted">· {role}</span>
				{/if}
			</div>
			<p class="mt-1.5 text-sm leading-relaxed text-foreground-muted">{content}</p>
		</div>
	</div>
</div>
<script lang="ts">
	import { cn } from '$lib/utils/cn';

	type Props = {
		avatar?: string;
		name: string;
		role?: string;
		content: string;
		class?: string;
	};

	let {
		avatar = '',
		name,
		role = '',
		content,
		class: className = ''
	}: Props = $props();

	const initials = $derived(
		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- name is typed as required, but callers outside this codebase's type-checking (JS, dynamic props, unchecked SSR data) can still pass undefined at runtime.
		(name ?? '')
			.split(' ')
			.map((n) => n[0])
			.slice(0, 2)
			.join('')
			.toUpperCase()
	);
</script>

<div
	class={cn(
		'inset-shadow rounded-md bg-background p-4',
		className
	)}
>
	<div class="flex items-start gap-3">
		{#if avatar}
			<img src={avatar} alt={name} class="size-10 rounded-full object-cover" />
		{:else}
			<div class="flex size-10 items-center justify-center rounded-full bg-accent/10 text-sm font-medium text-accent">
				{initials}
			</div>
		{/if}
		<div class="flex-1">
			<div class="flex items-center gap-2">
				<span class="text-sm font-medium text-foreground">{name}</span>
				{#if role}
					<span class="text-xs text-foreground-muted">· {role}</span>
				{/if}
			</div>
			<p class="mt-1.5 text-sm leading-relaxed text-foreground-muted">{content}</p>
		</div>
	</div>
</div>

Примеры использования

С аватаром

Алексей Петров
Алексей Петров · Tech Lead

Отличный инструмент для автоматизации процессов. Интеграция прошла гладко.

<Testimonial
	name="Алексей Петров"
	role="Tech Lead"
	content="Отличный инструмент для автоматизации процессов."
	avatar="/avatar.jpg"
/>
<Testimonial
	name="Алексей Петров"
	role="Tech Lead"
	content="Отличный инструмент для автоматизации процессов."
	avatar="/avatar.jpg"
/>

Без аватара (автоинициалы)

МС
Мария Сидорова · Product Manager

Удобный интерфейс и отличная документация. Рекомендую!

<Testimonial
	name="Мария Сидорова"
	role="Product Manager"
	content="Удобный интерфейс и отличная документация."
/>
<Testimonial
	name="Мария Сидорова"
	role="Product Manager"
	content="Удобный интерфейс и отличная документация."
/>

Пропсы

Пропс Тип По умолчанию Описание
name string Имя автора
role string Роль/должность
content string Текст отзыва
avatar string URL аватара (опционально)
class string Дополнительные классы