feat: align icons and re-design sections layout
- TNL-11990: Icons Alignment - TNL-11982: Re-design Sections layout
This commit is contained in:
committed by
Muhammad Faraz Maqsood
parent
3c69733170
commit
27a2b1235e
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user