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
10 changes: 8 additions & 2 deletions src/components/common/CreatorBio.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useId, useState } from 'react';
import { cn } from '@/lib/utils';
import { lineClampClassFor } from '@/utils/lineClamp.utils';
import {
lineClampClassFor,
creatorCardSubtitleClampClass,
} from '@/utils/lineClamp.utils';

interface CreatorBioProps {
/** Raw bio string from the creator profile. Anything falsy or whitespace-only is treated as missing. */
Expand Down Expand Up @@ -141,7 +144,10 @@ const CreatorBio: React.FC<CreatorBioProps> = ({
const clampVariant: 'card' | 'profile' = shouldOfferCollapse
? 'card'
: variant;
const clampClass = lineClampClassFor(clampVariant, effectiveMaxLines);
const clampClass =
clampVariant === 'card'
? creatorCardSubtitleClampClass(effectiveMaxLines)
: lineClampClassFor(clampVariant, effectiveMaxLines);

const bioParagraph = (
<p
Expand Down
9 changes: 8 additions & 1 deletion src/components/home/TrendingCreatorCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { Course } from '@/services/course.service';

type Props = { creator: Course & { walletAddress: string } };
import { formatHolderCount } from '@/utils/numberFormat.utils';
import { cn } from '@/lib/utils';
import { creatorCardSubtitleClampClass } from '@/utils/lineClamp.utils';

export default function TrendingCreatorCard({ creator }: Props) {
const name = creator.title || 'Unnamed creator';
Expand Down Expand Up @@ -33,7 +35,12 @@ export default function TrendingCreatorCard({ creator }: Props) {
</h3>

{creator.description && (
<p className="mt-2 line-clamp-2 font-jakarta text-xs leading-relaxed text-gray-500">
<p
className={cn(
'mt-2 font-jakarta text-xs leading-relaxed text-gray-500',
creatorCardSubtitleClampClass()
)}
>
{creator.description}
</p>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,22 @@ describe('TrendingCreatorCard integration (#484)', () => {

expect(screen.getByText('999')).toBeInTheDocument();
});

it('applies creator card subtitle clamp helper class to description', () => {
render(
<MemoryRouter>
<TrendingCreatorCard
creator={{
...baseCreator,
description: 'A long subtitle description for testing clamp helper.',
}}
/>
</MemoryRouter>
);

const descriptionElement = screen.getByText(
'A long subtitle description for testing clamp helper.'
);
expect(descriptionElement.className).toMatch(/\bline-clamp-2\b/);
});
});
61 changes: 61 additions & 0 deletions src/utils/__tests__/lineClamp.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, expect, it } from 'vitest';
import {
lineClampClassFor,
creatorCardSubtitleClampClass,
DEFAULT_CREATOR_CARD_SUBTITLE_MAX_LINES,
} from '../lineClamp.utils';

describe('lineClamp.utils', () => {
describe('DEFAULT_CREATOR_CARD_SUBTITLE_MAX_LINES', () => {
it('defaults to 2 lines for creator card subtitles', () => {
expect(DEFAULT_CREATOR_CARD_SUBTITLE_MAX_LINES).toBe(2);
});
});

describe('creatorCardSubtitleClampClass', () => {
it('returns line-clamp-2 by default when no parameter is passed', () => {
expect(creatorCardSubtitleClampClass()).toBe('line-clamp-2');
});

it('returns correct line-clamp class for specified line counts', () => {
expect(creatorCardSubtitleClampClass(1)).toBe('line-clamp-1');
expect(creatorCardSubtitleClampClass(2)).toBe('line-clamp-2');
expect(creatorCardSubtitleClampClass(3)).toBe('line-clamp-3');
expect(creatorCardSubtitleClampClass(4)).toBe('line-clamp-4');
expect(creatorCardSubtitleClampClass(5)).toBe('line-clamp-5');
expect(creatorCardSubtitleClampClass(6)).toBe('line-clamp-6');
});

it('caps higher values at line-clamp-6 to keep card heights bounded', () => {
expect(creatorCardSubtitleClampClass(7)).toBe('line-clamp-6');
expect(creatorCardSubtitleClampClass(100)).toBe('line-clamp-6');
});

it('returns empty string for null, undefined, 0, or negative values', () => {
expect(creatorCardSubtitleClampClass(null)).toBe('');
expect(creatorCardSubtitleClampClass(0)).toBe('');
expect(creatorCardSubtitleClampClass(-1)).toBe('');
});
});

describe('lineClampClassFor', () => {
it('returns empty string for profile variant', () => {
expect(lineClampClassFor('profile', 3)).toBe('');
expect(lineClampClassFor('profile', null)).toBe('');
expect(lineClampClassFor('profile', undefined)).toBe('');
});

it('returns empty string for invalid maxLines', () => {
expect(lineClampClassFor('card', null)).toBe('');
expect(lineClampClassFor('card', undefined)).toBe('');
expect(lineClampClassFor('card', 0)).toBe('');
expect(lineClampClassFor('card', -5)).toBe('');
});

it('returns correct line-clamp classes for card variant', () => {
expect(lineClampClassFor('card', 1)).toBe('line-clamp-1');
expect(lineClampClassFor('card', 2)).toBe('line-clamp-2');
expect(lineClampClassFor('card', 3)).toBe('line-clamp-3');
});
});
});
15 changes: 15 additions & 0 deletions src/utils/lineClamp.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,18 @@ export const lineClampClassFor = (
return 'line-clamp-6';
}
};

/**
* Default line clamp count for creator card subtitles and bio descriptions.
*/
export const DEFAULT_CREATOR_CARD_SUBTITLE_MAX_LINES = 2;

/**
* Returns the Tailwind `line-clamp` class for creator card subtitles and bio descriptions.
* Uses `DEFAULT_CREATOR_CARD_SUBTITLE_MAX_LINES` (2 lines) if maxLines is omitted.
*/
export const creatorCardSubtitleClampClass = (
maxLines: number | null | undefined = DEFAULT_CREATOR_CARD_SUBTITLE_MAX_LINES
): string => {
return lineClampClassFor('card', maxLines);
};
Loading