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';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { uploadService } from '@/services/upload.service';
|
||||
@@ -32,7 +32,47 @@ function ProfessionalCard({
|
||||
const [imgError, setImgError] = useState(false);
|
||||
const placeholder = '/assets/icons/user-placeholder-icon.svg';
|
||||
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 (
|
||||
<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]">
|
||||
Expertise:
|
||||
</p>
|
||||
<div 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) => (
|
||||
<div ref={tagContainerRef} className="flex flex-wrap gap-1.5 mt-2 items-center max-h-[54px] overflow-hidden">
|
||||
{expertise.slice(0, visibleTagCount).map((tag, index) => (
|
||||
<span
|
||||
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"
|
||||
>
|
||||
{tag}
|
||||
</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">
|
||||
+{expertise.length - INITIAL_TAG_COUNT} more
|
||||
+{hiddenCount} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -254,7 +254,7 @@ export function CommonHeader() {
|
||||
Sign in
|
||||
</span>
|
||||
<span className="font-fractul font-bold text-[10px] text-[#00293d] leading-tight">
|
||||
Sign into connect
|
||||
Sign in to Connect
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user