client/src/inline/inlineModePinboardToggle.tsx (167 lines of code) (raw):
import ReactDOM from "react-dom";
import React, { useEffect, useMemo } from "react";
import { css } from "@emotion/react";
import root from "react-shadow/emotion";
import { composer, pinboard, pinMetal } from "../../colours";
import { PinboardIdWithItemCounts } from "shared/graphql/graphql";
import { palette, space } from "@guardian/source-foundations";
import { agateSans } from "../../fontNormaliser";
import { useGlobalStateContext } from "../globalState";
import { SvgSpinner } from "@guardian/source-react-components";
export const COUNT_COLUMNS_MIN_WIDTH = 25;
interface InlineModePinboardToggleProps {
node: HTMLElement;
pinboardId: string;
counts: PinboardIdWithItemCounts | undefined;
isSelected: boolean;
setMaybeSelectedPinboardId: (newId: string | null) => void;
}
const rowHighlightBoxShadowStyle = `inset 0px -6px 0px -3px ${pinboard[500]}, inset 0px 6px 0px -3px ${pinboard[500]}`;
const InlineModePinboardToggle = ({
node,
pinboardId,
counts,
isSelected,
setMaybeSelectedPinboardId,
}: InlineModePinboardToggleProps) => {
const { unreadFlags } = useGlobalStateContext();
useEffect(() => {
const row = node.parentElement;
if (isSelected && row) {
row.style.boxShadow = rowHighlightBoxShadowStyle;
row.style.position = "sticky";
row.style.top = "68px";
row.style.bottom = "0px";
row.style.zIndex = "3";
const groupHeadingTHs = document.querySelectorAll<HTMLElement>(
".content-list__group-heading"
);
let hasFoundThisNodesGroupIndex = false;
for (const groupHeadingTH of Array.from(groupHeadingTHs)) {
if (groupHeadingTH.dataset.groupTitle === node.dataset.groupTitle) {
groupHeadingTH.style.bottom = `${row.offsetHeight - 1}px`;
hasFoundThisNodesGroupIndex = true;
} else if (hasFoundThisNodesGroupIndex) {
//i.e. group headings after the row we're making sticky
groupHeadingTH.style.top = `${row.offsetHeight + 68 - 1}px`;
}
}
return () => {
if (row) {
row.style.boxShadow = "";
row.style.position = "";
row.style.top = "";
row.style.bottom = "";
row.style.zIndex = "";
}
groupHeadingTHs.forEach((groupHeadingTH) => {
groupHeadingTH.style.top = "";
groupHeadingTH.style.bottom = "";
});
};
}
}, [isSelected]);
return (
<root.div>
<div
onClick={(event) => {
event.stopPropagation();
setMaybeSelectedPinboardId(isSelected ? null : pinboardId);
}}
css={css`
${agateSans.xxsmall()};
font-size: 11px;
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
white-space: nowrap;
color: ${pinMetal};
border-radius: ${space[1]}px;
min-height: 18px;
padding: 2px 12px 2px 3px;
margin: 0 3px;
background-color: ${isSelected ? pinboard["500"] : "none"};
color: ${palette.neutral[86]};
&:hover {
background-color: ${pinboard[isSelected ? "800" : "500"]};
color: ${palette.neutral[0]};
}
`}
>
{!!counts?.unreadCount && unreadFlags?.[pinboardId] !== false && (
<span
css={css`
text-align: right;
min-width: ${COUNT_COLUMNS_MIN_WIDTH}px;
`}
>
<span
css={css`
display: inline-block;
border-radius: 10px;
background-color: ${composer.primary[400]};
padding: 0 2px;
margin: 1px;
min-width: 14px;
color: ${palette.neutral[100]};
text-align: center;
font-weight: bold;
`}
>
{counts.unreadCount}
</span>
</span>
)}
<span
css={css`
display: inline-block;
min-width: ${COUNT_COLUMNS_MIN_WIDTH}px;
text-align: right;
${counts?.totalCount === 0
? ""
: `font-weight: bold; color: ${palette.neutral[0]};`}
`}
>
{counts?.totalCount}
</span>
{counts?.totalCount === undefined && (
<div
css={css`
svg {
width: 15px;
height: 15px;
circle {
stroke: ${palette.neutral[86]};
}
path {
stroke: ${palette.neutral[60]};
}
}
`}
>
<SvgSpinner />
</div>
)}
</div>
</root.div>
);
};
export const InlineModePinboardTogglePortal = (
props: InlineModePinboardToggleProps
) =>
useMemo(
// memo here ensures we don't call ReactDOM.createPortal for all re-renders, only meaningful prop changes
// otherwise performance is terrible with lots of rows
() =>
ReactDOM.createPortal(
<InlineModePinboardToggle {...props} />,
props.node
),
[
props.node,
props.pinboardId,
props.counts?.unreadCount,
props.counts?.totalCount,
props.isSelected,
]
);