Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions src/common-components/info-tool-tip.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.info-icon-wrapper {
position: relative;
display: inline-block;
cursor: pointer;
}

.info-icon {
color: #555;
}

.tooltip-text {
visibility: hidden;
background-color: #333;
color: #fff;
padding: 8px 10px;
border-radius: 4px;
position: absolute;
left: 120%;
top: 50%;
transform: translateY(-50%);
opacity: 0;
transition: opacity 0.3s ease-in-out;
white-space: nowrap;
font-size: 0.85rem;
z-index: 10;
}

.info-icon-wrapper:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
17 changes: 17 additions & 0 deletions src/common-components/info-tool-tip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import InfoIcon from '@mui/icons-material/Info';
import './info-tool-tip.css';

const InfoTooltip = ({ text }) => {
return (
<div
className="info-icon-wrapper"
tabIndex="0"
aria-describedby="tooltip-text"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this here when there's no element with an id of tooltip-text for it to refer to?

>
<InfoIcon className="info-icon" />
<span className="tooltip-text">{text}</span>
Comment on lines +11 to +12
Copy link

Copilot AI Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tooltip lacks ARIA attributes and keyboard support; consider adding role="tooltip" and a focusable wrapper or aria-label.

Suggested change
<InfoIcon className="info-icon" />
<span className="tooltip-text">{text}</span>
<div
className="info-icon-wrapper"
tabIndex="0"
aria-describedby="tooltip-text"
>
<InfoIcon className="info-icon" />
</div>
<span
id="tooltip-text"
className="tooltip-text"
role="tooltip"
>
{text}
</span>

Copilot uses AI. Check for mistakes.

Comment on lines +4 to +12
Copy link

Copilot AI Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a static id 'tooltip-text' for aria-describedby can lead to duplicate IDs when multiple tooltips are rendered. Consider generating unique IDs per instance or using aria-label with a role="tooltip" on the tooltip element to improve screen reader accessibility.

Suggested change
const InfoTooltip = ({ text }) => {
return (
<div
className="info-icon-wrapper"
tabIndex="0"
aria-describedby="tooltip-text"
>
<InfoIcon className="info-icon" />
<span className="tooltip-text">{text}</span>
import { useId } from 'react';
const InfoTooltip = ({ text }) => {
const tooltipId = useId();
return (
<div
className="info-icon-wrapper"
tabIndex="0"
aria-describedby={tooltipId}
>
<InfoIcon className="info-icon" />
<span id={tooltipId} className="tooltip-text">{text}</span>

Copilot uses AI. Check for mistakes.

</div>
);
};

export default InfoTooltip;
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,8 @@ li.blocking-list-entry:nth-child(even) {
}

.blocking-panel-header {
padding: 0.5em;
padding: 0.3em 0.7em 0.3em 0.4em;
margin: 0;
margin-top: 0.5em;
margin-bottom: 0;
}

Expand Down
70 changes: 42 additions & 28 deletions src/detail-panels/blocked-by-lists/blocked-by-lists-index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import { SearchHeaderDebounced } from '../history/search-header';
import { VisibleWithDelay } from '../../common-components/visible';
import { resolveHandleOrDID } from '../../api';
import { useAccountResolver } from '../account-resolver';
import InfoTooltip from '../../common-components/info-tool-tip';

export function BlockedByLists() {
const accountQuery = useAccountResolver();
const shortHandle = accountQuery.data?.shortHandle;
const { data, fetchNextPage, hasNextPage, isLoading, isFetching } = useBlockedByLists(shortHandle);
const { data: totalData, isLoading: isLoadingTotal } = useBlockedByListsTotal(shortHandle);
const { data, fetchNextPage, hasNextPage, isLoading, isFetching } =
useBlockedByLists(shortHandle);
const { data: totalData, isLoading: isLoadingTotal } =
useBlockedByListsTotal(shortHandle);

const [searchParams, setSearchParams] = useSearchParams();
const [tick, setTick] = useState(0);
Expand All @@ -29,54 +32,64 @@ export function BlockedByLists() {
const listsTotal = totalData?.count;
const listPages = data?.pages || [];
const allLists = listPages.flatMap((page) => page.blocklist);
const filteredLists = !search ? allLists : matchSearch(allLists, search, () => setTick(tick + 1));
const filteredLists = !search
? allLists
: matchSearch(allLists, search, () => setTick(tick + 1));

// Show loader for initial load
if (isLoading) {
return (
<div style={{ padding: '1em', textAlign: 'center', opacity: '0.5' }}>
<CircularProgress size="1.5em" />
<CircularProgress size="1.5em" />
<div style={{ marginTop: '0.5em' }}>
{'Loading blocked by lists...'}
</div>
</div>
);
}

const shouldShowLoadMore = hasNextPage && (!search || filteredLists.length > 0);
const shouldShowLoadMore =
hasNextPage && (!search || filteredLists.length > 0);

return (
<>
<div>
<div style={showSearch ? undefined : { display: 'none' }}>
<SearchHeaderDebounced
label='Search'
setQ />
<SearchHeaderDebounced label="Search" setQ />
</div>
</div>

<h3 className='lists-header'>
{(isLoadingTotal && !listsTotal) && <span style={{ opacity: 0.5 }}>{"Counting block lists..."}</span>}
{listsTotal ?
<h3 className="lists-header">
<div style={{ fontWeight: '400', paddingBottom: '0.2em' }}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with copilot: if this same div and style is repeated everywhere we use the tooltip component, then just include it inside the tooltip directly

<InfoTooltip text="this page shows panel members that blocked you via lists" />
</div>
{isLoadingTotal && !listsTotal && (
<span style={{ opacity: 0.5 }}>{'Counting block lists...'}</span>
)}
{listsTotal ? (
<>
{`Blocked by ${Intl.NumberFormat().format(listsTotal)} users via lists`}
<span className='panel-toggles'>
{!showSearch &&
{`Blocked by ${Intl.NumberFormat().format(
listsTotal
)} users via lists`}
<span className="panel-toggles">
{!showSearch && (
<Button
size='small'
className='panel-show-search'
title='Search'
onClick={() => setShowSearch(true)}><SearchIcon /></Button>
}
size="small"
className="panel-show-search"
title="Search"
onClick={() => setShowSearch(true)}
>
<SearchIcon />
</Button>
)}
</span>
</> :
isLoadingTotal ? null : 'Not blocked by any users via lists'
}
</>
) : isLoadingTotal ? null : (
'Not blocked by any users via lists'
)}
</h3>

<BlockListsView
list={filteredLists}
handle={shortHandle} />
<BlockListsView list={filteredLists} handle={shortHandle} />

{shouldShowLoadMore && (
<VisibleWithDelay
Expand All @@ -99,11 +112,12 @@ export function BlockedByLists() {
*/
function matchSearch(blocklist, search, redraw) {
const searchLowercase = search.toLowerCase();
const filtered = blocklist.filter(entry => {
if ((entry.list_name || '').toLowerCase().includes(searchLowercase)) return true;
const filtered = blocklist.filter((entry) => {
if ((entry.list_name || '').toLowerCase().includes(searchLowercase))
return true;

resolveHandleOrDID(entry.list_owner).then(redraw);
return false;
});
return filtered;
}
}
4 changes: 4 additions & 0 deletions src/detail-panels/blocked-by/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BlockPanelGeneric } from '../block-panel-generic';
import { localise } from '../../localisation';
import { useSingleBlocklistCount } from '../../api/blocklist';
import { useAccountResolver } from '../account-resolver';
import InfoTooltip from '../../common-components/info-tool-tip';

export default function BlockedByPanel() {
const accountQuery = useAccountResolver();
Expand All @@ -18,6 +19,9 @@ export default function BlockedByPanel() {
totalQuery={totalQuery}
header={({ count }) => (
<>
<div style={{ fontWeight: '400', paddingBottom: '0.2em' }}>
<InfoTooltip text="below list shows panel members that have blocked you" />
</div>
{localise(`Blocked by ${count.toLocaleString()}`, {
uk: `Блокують ${count.toLocaleString()}`,
})}
Expand Down
81 changes: 50 additions & 31 deletions src/detail-panels/blocking-lists/blocking-lists-index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import { SearchHeaderDebounced } from '../history/search-header';
import { VisibleWithDelay } from '../../common-components/visible';
import { resolveHandleOrDID } from '../../api';
import { useAccountResolver } from '../account-resolver';
import InfoTooltip from '../../common-components/info-tool-tip';

export function BlockingLists() {
const accountQuery = useAccountResolver();
const shortHandle = accountQuery.data?.shortHandle;
const { data, fetchNextPage, hasNextPage, isLoading, isFetching } = useBlockingLists(shortHandle);
const { data: totalData, isLoading: isLoadingTotal } = useBlockingListsTotal(shortHandle);
const { data, fetchNextPage, hasNextPage, isLoading, isFetching } =
useBlockingLists(shortHandle);
const { data: totalData, isLoading: isLoadingTotal } =
useBlockingListsTotal(shortHandle);

const [searchParams, setSearchParams] = useSearchParams();
const [tick, setTick] = useState(0);
Expand All @@ -29,53 +32,68 @@ export function BlockingLists() {
const listTotalBlocks = totalData?.count;
const listPages = data?.pages || [];
const allLists = listPages.flatMap((page) => page.blocklist);
const filteredLists = !search ? allLists : matchSearch(allLists, search, () => setTick(tick + 1));
const filteredLists = !search
? allLists
: matchSearch(allLists, search, () => setTick(tick + 1));

// Show loader for initial load
if (isLoading) {
return (
<div style={{ padding: '1em', textAlign: 'center', opacity: '0.5' }}>
<CircularProgress size="1.5em" />
<div style={{ marginTop: '0.5em' }}>
{'Loading blocking lists...'}
</div>
<CircularProgress size="1.5em" />
<div style={{ marginTop: '0.5em' }}>{'Loading blocking lists...'}</div>
</div>
);
}

const shouldShowLoadMore = hasNextPage && (!search || filteredLists.length > 0);
const shouldShowLoadMore =
hasNextPage && (!search || filteredLists.length > 0);

return (
<>
<div>
<div style={showSearch ? undefined : { display: 'none' }}>
<SearchHeaderDebounced
label='Search'
setQ />
<SearchHeaderDebounced label="Search" setQ />
</div>
</div>

<h3 className='lists-header'>
{(isLoadingTotal && !listTotalBlocks) && <span style={{ opacity: 0.5 }}>{"Counting lists..."}</span>}
{listTotalBlocks ?
<div
style={{
fontWeight: '400',
// paddingBottom: '0.2em',
Copy link

Copilot AI Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Commented-out style rules can clutter the code. If this padding is no longer needed, remove the commented line to keep the code clean.

Suggested change
// paddingBottom: '0.2em',

Copilot uses AI. Check for mistakes.

paddingLeft: '0.5em',
paddingTop: '0.3em',
}}
>
<InfoTooltip text="this page shows panel members you have blocked via lists" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the wording on some of these are confusing to me. what is a "panel member"?

these are also all worded using the 2nd person voice, assuming the reader is only viewing their own profile: "members YOU have blocked"
I feel it may also add some confusion if someone is relying on these to understand the tab contents from a different profile than their own.

</div>
<h3 className="lists-header">
{isLoadingTotal && !listTotalBlocks && (
<span style={{ opacity: 0.5 }}>{'Counting lists...'}</span>
)}
{listTotalBlocks ? (
<>
{`Blocking ${Intl.NumberFormat().format(listTotalBlocks)} total users via lists`}
<span className='panel-toggles'>
{!showSearch &&
{`Blocking ${Intl.NumberFormat().format(
listTotalBlocks
)} total users via lists`}
<span className="panel-toggles">
{!showSearch && (
<Button
size='small'
className='panel-show-search'
title='Search'
onClick={() => setShowSearch(true)}><SearchIcon /></Button>
}
size="small"
className="panel-show-search"
title="Search"
onClick={() => setShowSearch(true)}
>
<SearchIcon />
</Button>
)}
</span>
</> :
isLoadingTotal ? null : 'Not blocking any users via lists'
}
</>
) : isLoadingTotal ? null : (
'Not blocking any users via lists'
)}
</h3>

<BlockListsView
list={filteredLists} />
<BlockListsView list={filteredLists} />

{shouldShowLoadMore && (
<VisibleWithDelay
Expand All @@ -98,11 +116,12 @@ export function BlockingLists() {
*/
function matchSearch(blocklist, search, redraw) {
const searchLowercase = search.toLowerCase();
const filtered = blocklist.filter(entry => {
if ((entry.list_name || '').toLowerCase().includes(searchLowercase)) return true;
const filtered = blocklist.filter((entry) => {
if ((entry.list_name || '').toLowerCase().includes(searchLowercase))
return true;

resolveHandleOrDID(entry.list_owner).then(redraw);
return false;
});
return filtered;
}
}
4 changes: 4 additions & 0 deletions src/detail-panels/blocking/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BlockPanelGeneric } from '../block-panel-generic';
import { localise } from '../../localisation';
import { useBlocklistCount } from '../../api/blocklist';
import { useAccountResolver } from '../account-resolver';
import InfoTooltip from '../../common-components/info-tool-tip';

export default function BlockingPanel() {
const accountQuery = useAccountResolver();
Expand All @@ -18,6 +19,9 @@ export default function BlockingPanel() {
totalQuery={totalQuery}
header={({ count }) => (
<>
<div style={{ fontWeight: '400', paddingBottom: '0.2em' }}>
<InfoTooltip text="This page shows panel members you have blocked." />
</div>
{localise(
`Blocking ${
totalQuery.isLoading ? 'loading...' : count.toLocaleString()
Expand Down
10 changes: 10 additions & 0 deletions src/detail-panels/history/history-panel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Button, Tooltip, IconButton } from '@mui/material';
import InfoIcon from '@mui/icons-material/Info';
import './history-panel.css';
import { useAccountResolver } from '../account-resolver';
import InfoTooltip from '../../common-components/info-tool-tip';

export default function HistoryPanel() {
const accountQuery = useAccountResolver();
Expand All @@ -21,6 +22,15 @@ export default function HistoryPanel() {

return (
<>
<div
style={{
fontWeight: '400',
paddingTop: '0.3em',
Copy link

Copilot AI Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This inline style is duplicated across panels; consider extracting into a shared CSS class or wrapper component.

Copilot uses AI. Check for mistakes.

paddingLeft: '0.5em',
}}
>
<InfoTooltip text="this page shows your posts" />
</div>
<SearchHeaderDebounced
label={localise('Search history', { uk: 'Шукати в історії' })}
setQ
Expand Down
Loading