diff --git a/src/components/common/CreatorBio.tsx b/src/components/common/CreatorBio.tsx index ae3f60c5..b87480dc 100644 --- a/src/components/common/CreatorBio.tsx +++ b/src/components/common/CreatorBio.tsx @@ -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. */ @@ -141,7 +144,10 @@ const CreatorBio: React.FC = ({ const clampVariant: 'card' | 'profile' = shouldOfferCollapse ? 'card' : variant; - const clampClass = lineClampClassFor(clampVariant, effectiveMaxLines); + const clampClass = + clampVariant === 'card' + ? creatorCardSubtitleClampClass(effectiveMaxLines) + : lineClampClassFor(clampVariant, effectiveMaxLines); const bioParagraph = (

{creator.description && ( -

+

{creator.description}

)} diff --git a/src/components/home/__tests__/TrendingCreatorCard.integration.test.tsx b/src/components/home/__tests__/TrendingCreatorCard.integration.test.tsx index cc770b61..de51bc4e 100644 --- a/src/components/home/__tests__/TrendingCreatorCard.integration.test.tsx +++ b/src/components/home/__tests__/TrendingCreatorCard.integration.test.tsx @@ -52,4 +52,22 @@ describe('TrendingCreatorCard integration (#484)', () => { expect(screen.getByText('999')).toBeInTheDocument(); }); + + it('applies creator card subtitle clamp helper class to description', () => { + render( + + + + ); + + const descriptionElement = screen.getByText( + 'A long subtitle description for testing clamp helper.' + ); + expect(descriptionElement.className).toMatch(/\bline-clamp-2\b/); + }); }); diff --git a/src/utils/__tests__/lineClamp.utils.test.ts b/src/utils/__tests__/lineClamp.utils.test.ts new file mode 100644 index 00000000..23dd783d --- /dev/null +++ b/src/utils/__tests__/lineClamp.utils.test.ts @@ -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'); + }); + }); +}); diff --git a/src/utils/lineClamp.utils.ts b/src/utils/lineClamp.utils.ts index f7f3c1c7..37ec7a23 100644 --- a/src/utils/lineClamp.utils.ts +++ b/src/utils/lineClamp.utils.ts @@ -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); +};