feat: align icons and re-design sections layout

- TNL-11990: Icons Alignment
- TNL-11982: Re-design Sections layout
This commit is contained in:
Muhammad Faraz Maqsood
2025-05-23 14:33:50 +05:00
committed by Muhammad Faraz Maqsood
parent 3c69733170
commit 27a2b1235e
3 changed files with 160 additions and 72 deletions

View File

@@ -143,19 +143,27 @@
align-items: center;
font-size: small;
gap: 40px;
}
span {
.section-collapsible-header-action-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
color: #000000;
font-variant-numeric: lining-nums tabular-nums;
font-family: Inter, sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 28px;
p{
width: 20px;
margin: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
color: #000000;
font-variant-numeric: lining-nums tabular-nums;
font-family: Inter, sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 28px;
align-self: center;
}
}
@@ -168,7 +176,7 @@
}
.section-collapsible-item {
margin-right: -24px;
margin-right: -36px;
}
.section-collapsible-item-body {
@@ -273,3 +281,25 @@
margin-top: 1.5rem !important;
}
}
.open-section-rounded {
border: .5px solid rgb(0 0 0 / .15);
&.is-open {
&:not(:first-child) {
border-radius: 8px;
}
border-radius: 8px;
}
}
.closed-section-rounded-top {
border-top-left-radius: 8px !important;
border-top-right-radius: 8px !important;
}
.closed-section-rounded-bottom {
border-bottom-left-radius: 8px !important;
border-bottom-right-radius: 8px !important;
}

View File

@@ -1,4 +1,9 @@
import { useState, useMemo, FC } from 'react';
import {
useEffect,
useState,
useMemo,
FC,
} from 'react';
import {
Card,
Chip,
@@ -43,6 +48,7 @@ const ScanResults: FC<Props> = ({ data }) => {
externalForbiddenLinks: false,
};
const [filters, setFilters] = useState(initialFilters);
const [openStates, setOpenStates] = useState<boolean[]>([]);
const [buttonRef, setButtonRef] = useState<HTMLButtonElement | null>(null);
const {
@@ -56,17 +62,50 @@ const ScanResults: FC<Props> = ({ data }) => {
add, remove, set, clear,
}] = useCheckboxSetValues(activeFilters);
useEffect(() => {
setOpenStates(data?.sections ? data.sections.map(() => false) : []);
}, [data?.sections]);
if (!data?.sections) {
return <InfoCard text={intl.formatMessage(messages.noBrokenLinksCard)} />;
}
const { sections } = data;
const handleToggle = (index: number) => {
setOpenStates(prev => prev.map((isOpened, i) => (i === index ? !isOpened : isOpened)));
};
const filterOptions = [
{ name: intl.formatMessage(messages.brokenLabel), value: 'brokenLinks' },
{ name: intl.formatMessage(messages.manualLabel), value: 'externalForbiddenLinks' },
{ name: intl.formatMessage(messages.lockedLabel), value: 'lockedLinks' },
];
const shouldSectionRender = (sectionIndex: number): boolean => (
(!filters.brokenLinks && !filters.externalForbiddenLinks && !filters.lockedLinks)
|| (filters.brokenLinks && brokenLinksCounts[sectionIndex] > 0)
|| (filters.externalForbiddenLinks && externalForbiddenLinksCounts[sectionIndex] > 0)
|| (filters.lockedLinks && lockedLinksCounts[sectionIndex] > 0)
);
const findPreviousVisibleSection = (currentIndex: number): number => {
let prevIndex = currentIndex - 1;
while (prevIndex >= 0) {
if (shouldSectionRender(prevIndex)) {
return prevIndex;
}
prevIndex--;
}
return -1;
};
const findNextVisibleSection = (currentIndex: number): number => {
let nextIndex = currentIndex + 1;
while (nextIndex < sections.length) {
if (shouldSectionRender(nextIndex)) {
return nextIndex;
}
nextIndex++;
}
return -1;
};
return (
<div className="scan-results">
@@ -138,53 +177,58 @@ const ScanResults: FC<Props> = ({ data }) => {
)}
{sections?.map((section, index) => {
const shouldRenderSection = (
(!filters.brokenLinks && !filters.externalForbiddenLinks && !filters.lockedLinks)
|| (filters.brokenLinks && brokenLinksCounts[index] > 0)
|| (filters.externalForbiddenLinks && externalForbiddenLinksCounts[index] > 0)
|| (filters.lockedLinks && lockedLinksCounts[index] > 0)
);
if (shouldRenderSection) {
hasSectionsRendered = true;
return (
<SectionCollapsible
key={section.id}
title={section.displayName}
brokenNumber={brokenLinksCounts[index]}
manualNumber={externalForbiddenLinksCounts[index]}
lockedNumber={lockedLinksCounts[index]}
className="section-collapsible-header"
>
{section.subsections.map((subsection) => (
<>
{subsection.units.map((unit) => {
if (
(!filters.brokenLinks && !filters.externalForbiddenLinks && !filters.lockedLinks)
|| (filters.brokenLinks && unit.blocks.some(block => block.brokenLinks.length > 0))
|| (filters.externalForbiddenLinks
&& unit.blocks.some(block => block.externalForbiddenLinks.length > 0))
|| (filters.lockedLinks && unit.blocks.some(block => block.lockedLinks.length > 0))
) {
return (
<div className="unit">
<BrokenLinkTable unit={unit} filters={filters} />
</div>
);
}
return null;
})}
</>
))}
</SectionCollapsible>
);
if (!shouldSectionRender(index)) {
return null;
}
return hasSectionsRendered === false ? (
<div className="no-results-found-container">
<h3 className="no-results-found">{intl.formatMessage(messages.noResultsFound)}</h3>
</div>
) : null;
hasSectionsRendered = true;
return (
<SectionCollapsible
index={index}
handleToggle={handleToggle}
isOpen={openStates[index]}
hasPrevAndIsOpen={index > 0 ? (() => {
const prevVisibleIndex = findPreviousVisibleSection(index);
return prevVisibleIndex >= 0 && openStates[prevVisibleIndex];
})() : true}
hasNextAndIsOpen={index < sections.length - 1 ? (() => {
const nextVisibleIndex = findNextVisibleSection(index);
return nextVisibleIndex >= 1 && openStates[nextVisibleIndex];
})() : true}
key={section.id}
title={section.displayName}
brokenNumber={brokenLinksCounts[index]}
manualNumber={externalForbiddenLinksCounts[index]}
lockedNumber={lockedLinksCounts[index]}
className="section-collapsible-header"
>
{section.subsections.map((subsection) => (
<>
{subsection.units.map((unit) => {
if (
(!filters.brokenLinks && !filters.externalForbiddenLinks && !filters.lockedLinks)
|| (filters.brokenLinks && unit.blocks.some(block => block.brokenLinks.length > 0))
|| (filters.externalForbiddenLinks
&& unit.blocks.some(block => block.externalForbiddenLinks.length > 0))
|| (filters.lockedLinks && unit.blocks.some(block => block.lockedLinks.length > 0))
) {
return (
<div className="unit">
<BrokenLinkTable unit={unit} filters={filters} />
</div>
);
}
return null;
})}
</>
))}
</SectionCollapsible>
);
})}
{hasSectionsRendered === false && (
<div className="no-results-found-container">
<h3 className="no-results-found">{intl.formatMessage(messages.noResultsFound)}</h3>
</div>
)}
</div>
);
};

View File

@@ -1,4 +1,4 @@
import { useState, FC } from 'react';
import { FC } from 'react';
import {
Collapsible,
Icon,
@@ -14,6 +14,11 @@ import lockedIcon from './lockedIcon';
import ManualIcon from './manualIcon';
interface Props {
index: number;
handleToggle: Function;
isOpen: boolean;
hasPrevAndIsOpen: boolean;
hasNextAndIsOpen: boolean;
title: string;
children: React.ReactNode;
brokenNumber: number;
@@ -23,10 +28,19 @@ interface Props {
}
const SectionCollapsible: FC<Props> = ({
title, children, brokenNumber = 0, manualNumber = 0, lockedNumber = 0, className = '',
index,
handleToggle,
isOpen,
hasPrevAndIsOpen,
hasNextAndIsOpen,
title,
children,
brokenNumber,
manualNumber,
lockedNumber,
className,
}) => {
const [isOpen, setIsOpen] = useState(false);
const styling = 'card-lg rounded-sm';
const styling = `card-lg open-section-rounded ${hasPrevAndIsOpen ? 'closed-section-rounded-top' : ''} ${hasNextAndIsOpen ? 'closed-section-rounded-bottom' : ''}`;
const collapsibleTitle = (
<div className={className}>
<div className="section-collapsible-header-item">
@@ -34,18 +48,18 @@ const SectionCollapsible: FC<Props> = ({
<strong>{title}</strong>
</div>
<div className="section-collapsible-header-actions">
<span>
<div className="section-collapsible-header-action-item">
<CustomIcon icon={LinkOff} message1={messages.brokenLabel} message2={messages.brokenInfoTooltip} />
{brokenNumber}
</span>
<span>
<p>{brokenNumber}</p>
</div>
<div className="section-collapsible-header-action-item">
<CustomIcon icon={ManualIcon} message1={messages.manualLabel} message2={messages.manualInfoTooltip} />
{manualNumber}
</span>
<span>
<p>{manualNumber}</p>
</div>
<div className="section-collapsible-header-action-item">
<CustomIcon icon={lockedIcon} message1={messages.lockedLabel} message2={messages.lockedInfoTooltip} />
{lockedNumber}
</span>
<p>{lockedNumber}</p>
</div>
</div>
</div>
);
@@ -63,7 +77,7 @@ const SectionCollapsible: FC<Props> = ({
iconWhenClosed=""
iconWhenOpen=""
open={isOpen}
onToggle={() => setIsOpen(!isOpen)}
onToggle={() => handleToggle(index)}
>
<Collapsible.Body className="section-collapsible-item-body">{children}</Collapsible.Body>
</Collapsible>