Refactor Timeline Seperator (#31937)
* Refactor TimelineSeparator to shared-components package • New TimelineSeparator component in packages/shared-components/ • Updated MessagePanel.tsx to import from shared-components * Fix copyright text * Timeline Unit Tests + Timeline Snapshot Tests * Imported correct timeline seperator * Update snapshots because of css update * Apply suggestion from @florianduros Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * Created className prop * Removal of element x unused css * Update snapshot because of Flex * Update snapshots because of Flex * Update css to correct values and compund name * Added letter spacing to timelineseperator * rremoval of letter spacing * added align center to flex to apply correct css changes * Update snapshots to reflect new css changes * Update snapshots to reflect css changes * Added letter-spacing to timeline seperator * Update snapshots after css update * update snapshots --------- Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
This commit is contained in:
@@ -18,6 +18,7 @@ export * from "./event-tiles/TextualEventView";
|
||||
export * from "./message-body/MediaBody";
|
||||
export * from "./message-body/DecryptionFailureBodyView";
|
||||
export * from "./message-body/ReactionsRowButtonTooltip";
|
||||
export * from "./message-body/TimelineSeparator/";
|
||||
export * from "./pill-input/Pill";
|
||||
export * from "./pill-input/PillInput";
|
||||
export * from "./room/RoomStatusBar";
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.timelineSeparator {
|
||||
clear: both;
|
||||
margin: var(--cpd-space-1x) 0;
|
||||
font: var(--cpd-font-body-md-regular);
|
||||
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
|
||||
.timelineSeparator > hr {
|
||||
flex: 1 1 0;
|
||||
height: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--cpd-color-gray-400);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import TimelineSeparator from "./TimelineSeparator";
|
||||
import styles from "./TimelineSeparator.module.css";
|
||||
|
||||
export default {
|
||||
title: "MessageBody/TimelineSeparator",
|
||||
component: TimelineSeparator,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
label: "Label Separator",
|
||||
children: "Timeline Separator",
|
||||
},
|
||||
} as Meta<typeof TimelineSeparator>;
|
||||
|
||||
const Template: StoryFn<typeof TimelineSeparator> = (args) => <TimelineSeparator {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
export const WithHtmlChild = Template.bind({});
|
||||
WithHtmlChild.args = {
|
||||
label: "Custom Label",
|
||||
children: (
|
||||
<h2 className={styles.timelineSeparator} aria-hidden="true">
|
||||
Thursday
|
||||
</h2>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithDateEvent = Template.bind({});
|
||||
WithDateEvent.args = {
|
||||
label: "Date Event Separator",
|
||||
children: "Wednesday",
|
||||
};
|
||||
|
||||
export const WithLateEvent = Template.bind({});
|
||||
WithLateEvent.args = {
|
||||
label: "Late Event Separator",
|
||||
children: "Fri, Jan 9, 2026",
|
||||
};
|
||||
|
||||
export const WithoutChildren = Template.bind({});
|
||||
WithoutChildren.args = {
|
||||
children: undefined,
|
||||
label: "Separator without children",
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { render } from "@test-utils";
|
||||
import { composeStories } from "@storybook/react-vite";
|
||||
import React from "react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import * as stories from "./TimelineSeparator.stories.tsx";
|
||||
|
||||
const { Default, WithHtmlChild, WithoutChildren, WithDateEvent, WithLateEvent } = composeStories(stories);
|
||||
|
||||
describe("TimelineSeparator", () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Snapshot tests", () => {
|
||||
it("renders the timeline separator in default state", () => {
|
||||
const { container } = render(<Default />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the timeline separator with HTML child", () => {
|
||||
const { container } = render(<WithHtmlChild />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the timeline separator with date event", () => {
|
||||
const { container } = render(<WithDateEvent />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the timeline separator with late event", () => {
|
||||
const { container } = render(<WithLateEvent />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the timeline separator without children", () => {
|
||||
const { container } = render(<WithoutChildren />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type PropsWithChildren } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import styles from "./TimelineSeparator.module.css";
|
||||
import { Flex } from "../..";
|
||||
|
||||
/**
|
||||
* Timeline separator props
|
||||
*/
|
||||
export interface TimelineSeparatorProps {
|
||||
/**
|
||||
* Accessible label for the separator (for example: "Today", "Yesterday", or a date).
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The CSS class name.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Optional children to render inside the timeline separator
|
||||
*/
|
||||
children?: PropsWithChildren["children"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic timeline separator component to render within a MessagePanel
|
||||
*
|
||||
* @param label the accessible label string describing the separator
|
||||
* @param children the children to draw within the timeline separator
|
||||
*/
|
||||
const TimelineSeparator: React.FC<TimelineSeparatorProps> = ({ label, className, children }) => {
|
||||
// ARIA treats <hr/>s as separators, here we abuse them slightly so manually treat this entire thing as one
|
||||
return (
|
||||
<Flex
|
||||
className={classNames(className, styles.timelineSeparator)}
|
||||
role="separator"
|
||||
aria-label={label}
|
||||
align="center"
|
||||
>
|
||||
<hr role="none" />
|
||||
{children}
|
||||
<hr role="none" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimelineSeparator;
|
||||
@@ -0,0 +1,100 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator in default state 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="Label Separator"
|
||||
class="flex timelineSeparator"
|
||||
role="separator"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
Timeline Separator
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator with HTML child 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="Custom Label"
|
||||
class="flex timelineSeparator"
|
||||
role="separator"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
<h2
|
||||
aria-hidden="true"
|
||||
class="timelineSeparator"
|
||||
>
|
||||
Thursday
|
||||
</h2>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator with date event 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="Date Event Separator"
|
||||
class="flex timelineSeparator"
|
||||
role="separator"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
Wednesday
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator with late event 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="Late Event Separator"
|
||||
class="flex timelineSeparator"
|
||||
role="separator"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
Fri, Jan 9, 2026
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator without children 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="Separator without children"
|
||||
class="flex timelineSeparator"
|
||||
role="separator"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
export { default as TimelineSeparator, type TimelineSeparatorProps } from "./TimelineSeparator";
|
||||
Reference in New Issue
Block a user