Files
outline/app/scenes/Document/components/History/HighlightChangesControl.tsx
T
Tom Moor eefa8d4222 Add year headings to compare version select (#12138)
* Add year headings to compare version select

* Address review feedback on heading options

Use stable keys for heading options and set explicit displayName.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 19:04:35 -04:00

151 lines
4.2 KiB
TypeScript

import { format as formatDate } from "date-fns";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Text from "@shared/components/Text";
import { dateLocale } from "@shared/utils/date";
import { RevisionHelper } from "@shared/utils/RevisionHelper";
import type Document from "~/models/Document";
import type Event from "~/models/Event";
import Revision from "~/models/Revision";
import { InputSelect, type Option } from "~/components/InputSelect";
import Switch from "~/components/Switch";
import useUserLocale from "~/hooks/useUserLocale";
import { revisionCollaboratorText } from "./utils";
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
import { ConditionalFade } from "~/components/Fade";
export const COMPARE_TO_PREVIOUS = "previous";
interface Props {
showChanges: boolean;
onShowChangesToggle: (checked: boolean) => void;
items: (Revision | Event<Document>)[];
document?: Document;
selectedRevisionId?: string;
compareTo: string;
onCompareToChange: (value: string) => void;
}
export function HighlightChangesControl({
showChanges,
onShowChangesToggle,
items,
document,
selectedRevisionId,
compareTo,
onCompareToChange,
}: Props) {
const { t } = useTranslation();
const userLocale = useUserLocale();
const skipFadeRef = React.useRef(showChanges);
const compareOptions = React.useMemo((): Option[] => {
const revisionItems = items.filter(
(item): item is Revision => item instanceof Revision
);
const locale = dateLocale(userLocale);
const resolvedSelectedId =
selectedRevisionId === "latest" && document
? RevisionHelper.latestId(document.id)
: selectedRevisionId;
const options: Option[] = [
{
type: "item",
label: t("Previous version"),
value: COMPARE_TO_PREVIOUS,
},
];
const latestId = document
? RevisionHelper.latestId(document.id)
: undefined;
const currentYear = new Date().getFullYear();
let lastHeadingYear: number | undefined;
for (const rev of revisionItems) {
if (rev.id === resolvedSelectedId) {
continue;
}
const revDate = new Date(rev.createdAt);
const revYear = revDate.getFullYear();
if (revYear !== currentYear && revYear !== lastHeadingYear) {
options.push({
type: "heading",
label: String(revYear),
});
lastHeadingYear = revYear;
}
const dateLabel = formatDate(revDate, "MMM do, h:mm a", {
locale,
});
const collaboratorText = revisionCollaboratorText(rev, t);
options.push({
type: "item",
label: rev.name ?? dateLabel,
value: rev.id === latestId ? "latest" : rev.id,
description: collaboratorText,
});
}
return options;
}, [items, selectedRevisionId, document, userLocale, t]);
return (
<Content>
<ResizingHeightContainer>
<Text size="small" as="div" style={{ padding: 4 }}>
<Switch
label={t("Highlight changes")}
checked={showChanges}
onChange={onShowChangesToggle}
/>
</Text>
{showChanges && (
<ConditionalFade animate={!skipFadeRef.current}>
<StyledInputSelect
options={compareOptions}
value={compareTo}
onChange={onCompareToChange}
label={t("Compare to")}
displayValue={(item) => (
<>
<Text weight="bold">{t("Compare to")}</Text> {item?.label}
</>
)}
labelHidden
nude
short
/>
</ConditionalFade>
)}
</ResizingHeightContainer>
</Content>
);
}
const StyledInputSelect = styled(InputSelect)`
margin: -4px -9px -1px;
width: calc(100% + 18px);
border-top-left-radius: 0;
border-top-right-radius: 0;
position: relative;
inset-block-end: -1px;
height: 40px;
`;
const Content = styled.div`
margin: 0 16px 8px;
border: 1px solid ${(props) => props.theme.inputBorder};
border-radius: 6px;
padding: 8px 8px 0;
flex-shrink: 0;
`;