Sifa professional network frontend (Next.js, React, TailwindCSS)
sifa.id/
1'use client';
2
3import { Children, useId, useState } from 'react';
4
5import { CaretDown, CaretUp } from '@phosphor-icons/react';
6import { useTranslations } from 'next-intl';
7
8import { cn } from '@/lib/utils';
9
10interface SectionOverflowProps {
11 /** Max items to show before "Show N more" button */
12 maxVisible: number;
13 /** When true, show all items without disclosure (used for own profile editing) */
14 disableOverflow?: boolean;
15 children: React.ReactNode;
16}
17
18export function SectionOverflow({ maxVisible, disableOverflow, children }: SectionOverflowProps) {
19 const [expanded, setExpanded] = useState(false);
20 const overflowId = useId();
21 const t = useTranslations('sections');
22
23 const items = Children.toArray(children);
24
25 if (items.length <= maxVisible || disableOverflow) {
26 return <>{items}</>;
27 }
28
29 const visible = items.slice(0, maxVisible);
30 const overflow = items.slice(maxVisible);
31
32 return (
33 <>
34 {visible}
35 <div
36 id={overflowId}
37 className={cn(
38 'grid motion-safe:transition-[grid-template-rows] motion-safe:duration-200 motion-safe:ease-in-out',
39 expanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
40 )}
41 >
42 <div className="overflow-hidden">{overflow}</div>
43 </div>
44 <button
45 type="button"
46 aria-expanded={expanded}
47 aria-controls={overflowId}
48 onClick={() => setExpanded((prev) => !prev)}
49 className="mt-3 flex items-center gap-1 text-sm font-medium text-muted-foreground hover:text-foreground motion-safe:transition-colors"
50 >
51 {expanded ? (
52 <>
53 {t('showLess')}
54 <CaretUp size={16} aria-hidden="true" />
55 </>
56 ) : (
57 <>
58 {t('showMore', { count: overflow.length })}
59 <CaretDown size={16} aria-hidden="true" />
60 </>
61 )}
62 </button>
63 </>
64 );
65}