feat: implement dynamic row-based tag clipping in TopProfessionals and fix typo in CommonHeader
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { uploadService } from '@/services/upload.service';
|
import { uploadService } from '@/services/upload.service';
|
||||||
@@ -32,7 +32,47 @@ function ProfessionalCard({
|
|||||||
const [imgError, setImgError] = useState(false);
|
const [imgError, setImgError] = useState(false);
|
||||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||||
const isPlaceholder = imageUrl === placeholder;
|
const isPlaceholder = imageUrl === placeholder;
|
||||||
const INITIAL_TAG_COUNT = 4;
|
|
||||||
|
// Dynamic row-based tag clipping: show as many tags as fit in 2 rows, with "+N more" if overflow
|
||||||
|
const tagContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [visibleTagCount, setVisibleTagCount] = useState<number>(expertise.length);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const container = tagContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const measure = () => {
|
||||||
|
const items = Array.from(container.querySelectorAll<HTMLElement>('[data-tag]'));
|
||||||
|
if (items.length === 0) return;
|
||||||
|
|
||||||
|
const firstTop = items[0].offsetTop;
|
||||||
|
let secondTop: number | null = null;
|
||||||
|
let rowBreakIndex = items.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const top = items[i].offsetTop;
|
||||||
|
if (top > firstTop) {
|
||||||
|
if (secondTop === null) secondTop = top;
|
||||||
|
if (top > secondTop) {
|
||||||
|
rowBreakIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasOverflow = rowBreakIndex < items.length;
|
||||||
|
// If overflow, drop one tag to reserve space for "+N more" chip
|
||||||
|
const finalCount = hasOverflow ? Math.max(1, rowBreakIndex - 1) : rowBreakIndex;
|
||||||
|
setVisibleTagCount(finalCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
measure();
|
||||||
|
const ro = new ResizeObserver(measure);
|
||||||
|
ro.observe(container);
|
||||||
|
return () => ro.disconnect();
|
||||||
|
}, [expertise]);
|
||||||
|
|
||||||
|
const hiddenCount = expertise.length - visibleTagCount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-[15px] overflow-hidden shadow-[0px_4px_20px_rgba(0,0,0,0.08)] h-full flex flex-col">
|
<div className="bg-white rounded-[15px] overflow-hidden shadow-[0px_4px_20px_rgba(0,0,0,0.08)] h-full flex flex-col">
|
||||||
@@ -112,18 +152,19 @@ function ProfessionalCard({
|
|||||||
<p className="font-fractul font-bold text-[14px] leading-normal text-[#00293d]">
|
<p className="font-fractul font-bold text-[14px] leading-normal text-[#00293d]">
|
||||||
Expertise:
|
Expertise:
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-1.5 mt-2 items-center max-h-[54px] overflow-hidden">
|
<div ref={tagContainerRef} className="flex flex-wrap gap-1.5 mt-2 items-center max-h-[54px] overflow-hidden">
|
||||||
{expertise.slice(0, INITIAL_TAG_COUNT).map((tag, index) => (
|
{expertise.slice(0, visibleTagCount).map((tag, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
|
data-tag
|
||||||
className="border-[0.7px] border-[#00293d] rounded-[15px] px-[5px] h-[24px] flex items-center justify-center font-serif font-normal text-[13px] leading-[100%] text-[#00293d] whitespace-nowrap flex-shrink-0"
|
className="border-[0.7px] border-[#00293d] rounded-[15px] px-[5px] h-[24px] flex items-center justify-center font-serif font-normal text-[13px] leading-[100%] text-[#00293d] whitespace-nowrap flex-shrink-0"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{expertise.length > INITIAL_TAG_COUNT && (
|
{hiddenCount > 0 && (
|
||||||
<span className="bg-[#e58625]/15 text-[#e58625] rounded-[15px] px-2 h-[24px] flex items-center justify-center font-serif font-semibold text-[12px] leading-[100%] whitespace-nowrap flex-shrink-0">
|
<span className="bg-[#e58625]/15 text-[#e58625] rounded-[15px] px-2 h-[24px] flex items-center justify-center font-serif font-semibold text-[12px] leading-[100%] whitespace-nowrap flex-shrink-0">
|
||||||
+{expertise.length - INITIAL_TAG_COUNT} more
|
+{hiddenCount} more
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export function CommonHeader() {
|
|||||||
Sign in
|
Sign in
|
||||||
</span>
|
</span>
|
||||||
<span className="font-fractul font-bold text-[10px] text-[#00293d] leading-tight">
|
<span className="font-fractul font-bold text-[10px] text-[#00293d] leading-tight">
|
||||||
Sign into connect
|
Sign in to Connect
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user