Move clock into shared components (#30480)
* refactor: extract `formatSeconds` from `DateUtils` * refactor: move clock into shared-components * refactor: update clock imports * test(e2e): add screenshots
This commit is contained in:
29
src/shared-components/audio/Clock/Clock.stories.tsx
Normal file
29
src/shared-components/audio/Clock/Clock.stories.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector 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 { Clock } from "./Clock";
|
||||
|
||||
export default {
|
||||
title: "Audio/Clock",
|
||||
component: Clock,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
seconds: 20,
|
||||
},
|
||||
} as Meta<typeof Clock>;
|
||||
|
||||
const Template: StoryFn<typeof Clock> = (args) => <Clock {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
export const LotOfSeconds = Template.bind({});
|
||||
LotOfSeconds.args = {
|
||||
seconds: 99999999999999,
|
||||
};
|
||||
26
src/shared-components/audio/Clock/Clock.test.tsx
Normal file
26
src/shared-components/audio/Clock/Clock.test.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector 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 { composeStories } from "@storybook/react-vite";
|
||||
import { render } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
|
||||
import * as stories from "./Clock.stories.tsx";
|
||||
|
||||
const { Default, LotOfSeconds } = composeStories(stories);
|
||||
|
||||
describe("Clock", () => {
|
||||
it("renders the clock", () => {
|
||||
const { container } = render(<Default />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the clock with a lot of seconds", () => {
|
||||
const { container } = render(<LotOfSeconds />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
50
src/shared-components/audio/Clock/Clock.tsx
Normal file
50
src/shared-components/audio/Clock/Clock.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
|
||||
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 HTMLProps } from "react";
|
||||
import { Temporal } from "temporal-polyfill";
|
||||
|
||||
import { formatSeconds } from "../../utils/DateUtils";
|
||||
|
||||
export interface Props extends Pick<HTMLProps<HTMLSpanElement>, "aria-live" | "role"> {
|
||||
seconds: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clock which represents time periods rather than absolute time.
|
||||
* Simply converts seconds using formatSeconds().
|
||||
* Note that in this case hours will not be displayed, making it possible to see "82:29".
|
||||
*/
|
||||
export class Clock extends React.Component<Props> {
|
||||
public shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
|
||||
const currentFloor = Math.floor(this.props.seconds);
|
||||
const nextFloor = Math.floor(nextProps.seconds);
|
||||
return currentFloor !== nextFloor;
|
||||
}
|
||||
|
||||
private calculateDuration(seconds: number): string | undefined {
|
||||
if (isNaN(seconds)) return undefined;
|
||||
return new Temporal.Duration(0, 0, 0, 0, 0, 0, Math.round(seconds))
|
||||
.round({ smallestUnit: "seconds", largestUnit: "hours" })
|
||||
.toString();
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const { seconds, role } = this.props;
|
||||
return (
|
||||
<time
|
||||
dateTime={this.calculateDuration(seconds)}
|
||||
aria-live={this.props["aria-live"]}
|
||||
role={role}
|
||||
/* Keep class for backward compatibility with parent component */
|
||||
className="mx_Clock"
|
||||
>
|
||||
{formatSeconds(seconds)}
|
||||
</time>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Clock renders the clock 1`] = `
|
||||
<div>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT20S"
|
||||
>
|
||||
00:20
|
||||
</time>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Clock renders the clock with a lot of seconds 1`] = `
|
||||
<div>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT27777777777H46M39S"
|
||||
>
|
||||
27777777777:46:39
|
||||
</time>
|
||||
</div>
|
||||
`;
|
||||
8
src/shared-components/audio/Clock/index.tsx
Normal file
8
src/shared-components/audio/Clock/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector 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 { Clock } from "./Clock";
|
||||
35
src/shared-components/utils/DateUtils.ts
Normal file
35
src/shared-components/utils/DateUtils.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Formats a number of seconds into a human-readable string.
|
||||
* @param inSeconds
|
||||
*/
|
||||
export function formatSeconds(inSeconds: number): string {
|
||||
const isNegative = inSeconds < 0;
|
||||
inSeconds = Math.abs(inSeconds);
|
||||
|
||||
const hours = Math.floor(inSeconds / (60 * 60))
|
||||
.toFixed(0)
|
||||
.padStart(2, "0");
|
||||
const minutes = Math.floor((inSeconds % (60 * 60)) / 60)
|
||||
.toFixed(0)
|
||||
.padStart(2, "0");
|
||||
const seconds = Math.floor((inSeconds % (60 * 60)) % 60)
|
||||
.toFixed(0)
|
||||
.padStart(2, "0");
|
||||
|
||||
let output = "";
|
||||
if (hours !== "00") output += `${hours}:`;
|
||||
output += `${minutes}:${seconds}`;
|
||||
|
||||
if (isNegative) {
|
||||
output = "-" + output;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
Reference in New Issue
Block a user