mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
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>
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
InputSelectContent,
|
||||
InputSelectItem,
|
||||
InputSelectSeparator,
|
||||
InputSelectHeading,
|
||||
InputSelectTrigger,
|
||||
type TriggerButtonProps,
|
||||
} from "./primitives/InputSelect";
|
||||
@@ -35,6 +36,13 @@ type Separator = {
|
||||
type: "separator";
|
||||
};
|
||||
|
||||
type Heading = {
|
||||
/* Denotes a non-selectable heading rendered above a group of options. */
|
||||
type: "heading";
|
||||
/* Text shown as the heading label. */
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type Item = {
|
||||
/* Denotes a selectable option in the menu. */
|
||||
type: "item";
|
||||
@@ -48,7 +56,7 @@ export type Item = {
|
||||
icon?: React.ReactElement;
|
||||
};
|
||||
|
||||
export type Option = Item | Separator;
|
||||
export type Option = Item | Separator | Heading;
|
||||
|
||||
type Props = Omit<React.HTMLAttributes<HTMLButtonElement>, "onChange"> & {
|
||||
/* Options to display in the select menu. */
|
||||
@@ -118,6 +126,14 @@ export const InputSelect = React.forwardRef<HTMLButtonElement, Props>(
|
||||
return <InputSelectSeparator key={`separator-${idx}`} />;
|
||||
}
|
||||
|
||||
if (option.type === "heading") {
|
||||
return (
|
||||
<InputSelectHeading key={`heading-${option.label}`}>
|
||||
{option.label}
|
||||
</InputSelectHeading>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InputSelectItem key={option.value} value={option.value}>
|
||||
<Option option={option} optionsHaveIcon={optionsHaveIcon} />
|
||||
@@ -244,6 +260,14 @@ const MobileSelect = React.forwardRef<HTMLButtonElement, MobileSelectProps>(
|
||||
return <InputSelectSeparator key={`separator-${idx}`} />;
|
||||
}
|
||||
|
||||
if (option.type === "heading") {
|
||||
return (
|
||||
<InputSelectHeading key={`heading-${option.label}`}>
|
||||
{option.label}
|
||||
</InputSelectHeading>
|
||||
);
|
||||
}
|
||||
|
||||
const isSelected = option === selectedOption;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as InputSelectPrimitive from "@radix-ui/react-select";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Text from "@shared/components/Text";
|
||||
import { depths, s } from "@shared/styles";
|
||||
import type { Props as ButtonProps } from "~/components/Button";
|
||||
import { fadeAndSlideDown, fadeAndSlideUp } from "~/styles/animations";
|
||||
@@ -110,6 +111,31 @@ const Separator = styled.hr`
|
||||
margin: 6px 0;
|
||||
`;
|
||||
|
||||
/** Non-selectable heading rendered to group options in the menu. */
|
||||
const InputSelectHeading = React.forwardRef<
|
||||
HTMLSpanElement,
|
||||
{ children?: React.ReactNode }
|
||||
>(({ children }, ref) => (
|
||||
<InputSelectPrimitive.Group>
|
||||
<InputSelectPrimitive.Label asChild>
|
||||
<Heading ref={ref}>{children}</Heading>
|
||||
</InputSelectPrimitive.Label>
|
||||
</InputSelectPrimitive.Group>
|
||||
));
|
||||
InputSelectHeading.displayName = "InputSelectHeading";
|
||||
|
||||
const Heading = styled(Text).attrs({
|
||||
type: "tertiary",
|
||||
size: "xsmall",
|
||||
weight: "bold",
|
||||
})`
|
||||
display: block;
|
||||
padding-block: 8px 4px;
|
||||
padding-inline: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
`;
|
||||
|
||||
/** Styled components. */
|
||||
const StyledContent = styled(InputSelectPrimitive.Content)`
|
||||
z-index: ${depths.menu};
|
||||
@@ -143,4 +169,5 @@ export {
|
||||
InputSelectContent,
|
||||
InputSelectItem,
|
||||
InputSelectSeparator,
|
||||
InputSelectHeading,
|
||||
};
|
||||
|
||||
@@ -63,12 +63,26 @@ export function HighlightChangesControl({
|
||||
? 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 dateLabel = formatDate(new Date(rev.createdAt), "MMM do, h:mm a", {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user