diff --git a/packages/shared-components/src/message-body/DecryptionFailureBodyView/__snapshots__/DecryptionFailureBodyView.test.tsx.snap b/packages/shared-components/src/message-body/DecryptionFailureBodyView/__snapshots__/DecryptionFailureBodyView.test.tsx.snap
index 7bd5899caf..e5b1bcd1eb 100644
--- a/packages/shared-components/src/message-body/DecryptionFailureBodyView/__snapshots__/DecryptionFailureBodyView.test.tsx.snap
+++ b/packages/shared-components/src/message-body/DecryptionFailureBodyView/__snapshots__/DecryptionFailureBodyView.test.tsx.snap
@@ -40,16 +40,6 @@ exports[`DecryptionFailureBodyView > Should display "Unable to decrypt message a
`;
-exports[`DecryptionFailureBodyView > Should display with extra class names 1`] = `
-
{
+const MessageTimestampWrapperImpl = ({
+ onClick,
+ onContextMenu,
+ className,
+ ...rest
+}: MessageTimestampProps & { className?: string }): ReactNode => {
const vm = useMockedViewModel(rest, {
onClick,
onContextMenu,
});
- return ;
+ return ;
};
const MessageTimestampWrapper = withViewDocs(MessageTimestampWrapperImpl, MessageTimestampView);
@@ -69,12 +74,6 @@ export const HasInhibitTooltip: Story = {
},
};
-export const HasExtraClassNames: Story = {
- args: {
- className: "extra_class_1 extra_class_2",
- },
-};
-
export const HasHref: Story = {
args: {
href: "~",
diff --git a/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.test.tsx b/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.test.tsx
index f51b7ecc8b..f04d7d585e 100644
--- a/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.test.tsx
+++ b/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.test.tsx
@@ -21,7 +21,7 @@ import { MockViewModel } from "../../viewmodel/MockViewModel.ts";
import { I18nContext } from "../../utils/i18nContext.ts";
import { I18nApi } from "../../index.ts";
-const { Default, HasHref, HasExtraClassNames } = composeStories(stories);
+const { Default, HasHref } = composeStories(stories);
const renderWithI18n = (ui: React.ReactElement): ReturnType =>
render(ui, {
@@ -38,9 +38,16 @@ describe("MessageTimestampView", () => {
expect(container).toMatchSnapshot();
});
- it("renders the message timestamp with extra class names", async () => {
- const { container } = render();
- expect(container).toMatchSnapshot();
+ it("applies custom className to the timestamp element", async () => {
+ const vm = new MockViewModel({
+ ts: "04:58",
+ tsSentAt: "Thu, 17 Nov 2022, 4:58:32 pm",
+ });
+
+ renderWithI18n();
+
+ const target = screen.getByText("04:58");
+ expect(target).toHaveClass("extra_class_1", "extra_class_2");
});
it("renders the message timestamp with href", async () => {
diff --git a/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.tsx b/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.tsx
index 7ddb4a082c..a60f68e882 100644
--- a/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.tsx
+++ b/packages/shared-components/src/message-body/MessageTimestampView/MessageTimestampView.tsx
@@ -32,10 +32,6 @@ export interface MessageTimestampViewSnapshot {
* If set to true then no tooltip will be shown
*/
inhibitTooltip?: boolean;
- /**
- * Extra class name to apply to the component
- */
- className?: string;
/**
* If specified, will be rendered as an anchor bearing the href, a `span` element will be used otherwise
*/
@@ -55,6 +51,9 @@ export interface MessageTimestampViewActions {
/**
* The view model for the message timestamp.
+ *
+ * Snapshot data describes timestamp content and rendering behavior, while
+ * container styling is supplied via component props.
*/
export type MessageTimestampViewModel = ViewModel & MessageTimestampViewActions;
@@ -63,6 +62,10 @@ interface MessageTimestampViewProps {
* The view model for the message timestamp.
*/
vm: MessageTimestampViewModel;
+ /**
+ * Optional CSS class name to apply to the component.
+ */
+ className?: string;
}
/**
@@ -70,17 +73,18 @@ interface MessageTimestampViewProps {
*
* The view model provides the timestamp values and display options. The component
* can render as a link when `href` is set, and can show both sent-at and received-at
- * times in the tooltip when `tsReceivedAt` is provided.
+ * times in the tooltip when `tsReceivedAt` is provided. Use `className` for
+ * host-level styling.
*
* @example
* ```tsx
- *
+ *
* ```
*/
-export function MessageTimestampView({ vm }: Readonly): JSX.Element {
+export function MessageTimestampView({ vm, className }: Readonly): JSX.Element {
const { translate: _t } = useI18n();
- const { ts, tsSentAt, tsReceivedAt, inhibitTooltip, className, href } = useViewModel(vm);
+ const { ts, tsSentAt, tsReceivedAt, inhibitTooltip, href } = useViewModel(vm);
const onKeyDown = (event: KeyboardEvent): void => {
if (vm.onClick) {
diff --git a/packages/shared-components/src/message-body/MessageTimestampView/__snapshots__/MessageTimestampView.test.tsx.snap b/packages/shared-components/src/message-body/MessageTimestampView/__snapshots__/MessageTimestampView.test.tsx.snap
index 67ae66820e..4b896472f5 100644
--- a/packages/shared-components/src/message-body/MessageTimestampView/__snapshots__/MessageTimestampView.test.tsx.snap
+++ b/packages/shared-components/src/message-body/MessageTimestampView/__snapshots__/MessageTimestampView.test.tsx.snap
@@ -12,18 +12,6 @@ exports[`MessageTimestampView > renders the message timestamp in default state 1
`;
-exports[`MessageTimestampView > renders the message timestamp with extra class names 1`] = `
-
-
-
-`;
-
exports[`MessageTimestampView > renders the message timestamp with href 1`] = `
{
+}: WrapperProps & { children?: ReactNode; className?: string }): JSX.Element => {
const vm = useMockedViewModel(snapshotProps, {
onShowAllClick: onShowAllClick ?? fn(),
onAddReactionClick: onAddReactionClick ?? fn(),
onAddReactionContextMenu: onAddReactionContextMenu ?? fn(),
});
- return ;
+ return (
+
+ {children}
+
+ );
};
const meta = {
diff --git a/packages/shared-components/src/message-body/ReactionRow/ReactionsRow.test.tsx b/packages/shared-components/src/message-body/ReactionsRow/ReactionsRow.test.tsx
similarity index 83%
rename from packages/shared-components/src/message-body/ReactionRow/ReactionsRow.test.tsx
rename to packages/shared-components/src/message-body/ReactionsRow/ReactionsRow.test.tsx
index a6f28b9370..2ca1c53281 100644
--- a/packages/shared-components/src/message-body/ReactionRow/ReactionsRow.test.tsx
+++ b/packages/shared-components/src/message-body/ReactionsRow/ReactionsRow.test.tsx
@@ -68,7 +68,6 @@ describe("ReactionsRowView", () => {
showAddReactionButton: true,
addReactionButtonLabel: "Add reaction",
addReactionButtonVisible: true,
- children: 👍,
},
{
onShowAllClick,
@@ -77,7 +76,11 @@ describe("ReactionsRowView", () => {
},
) as ReactionsRowViewModel;
- render();
+ render(
+
+ 👍
+ ,
+ );
await user.click(screen.getByRole("button", { name: "Show all" }));
await user.click(screen.getByRole("button", { name: "Add reaction" }));
@@ -87,4 +90,16 @@ describe("ReactionsRowView", () => {
expect(onAddReactionClick).toHaveBeenCalledTimes(1);
expect(onAddReactionContextMenu).toHaveBeenCalledTimes(1);
});
+
+ it("applies custom className to the toolbar container", () => {
+ const vm = new MockViewModel({
+ ariaLabel: "Reactions",
+ isVisible: true,
+ addReactionButtonLabel: "Add reaction",
+ }) as ReactionsRowViewModel;
+
+ render();
+
+ expect(screen.getByRole("toolbar", { name: "Reactions" })).toHaveClass("custom-reactions-row", "another-class");
+ });
});
diff --git a/packages/shared-components/src/message-body/ReactionRow/ReactionsRowView.tsx b/packages/shared-components/src/message-body/ReactionsRow/ReactionsRowView.tsx
similarity index 94%
rename from packages/shared-components/src/message-body/ReactionRow/ReactionsRowView.tsx
rename to packages/shared-components/src/message-body/ReactionsRow/ReactionsRowView.tsx
index fffd9d6c1b..379cf58857 100644
--- a/packages/shared-components/src/message-body/ReactionRow/ReactionsRowView.tsx
+++ b/packages/shared-components/src/message-body/ReactionsRow/ReactionsRowView.tsx
@@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details.
*/
-import React, { type JSX, type MouseEventHandler, type ReactNode } from "react";
+import React, { type JSX, type MouseEventHandler, type PropsWithChildren } from "react";
import classNames from "classnames";
import { ReactionAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Tooltip } from "@vector-im/compound-web";
@@ -22,14 +22,6 @@ export interface ReactionsRowViewSnapshot {
* Controls whether the row should render at all.
*/
isVisible: boolean;
- /**
- * Reaction button elements to render in the row.
- */
- children?: ReactNode;
- /**
- * Optional CSS className for the row container.
- */
- className?: string;
/**
* Whether to render the "show all" button.
*/
@@ -79,14 +71,20 @@ export type ReactionsRowViewModel = ViewModel): JSX.Element {
+export function ReactionsRowView({ vm, className, children }: Readonly): JSX.Element {
const {
ariaLabel,
isVisible,
- children,
- className,
showAllButtonVisible,
showAllButtonLabel,
showAddReactionButton,
diff --git a/packages/shared-components/src/message-body/ReactionRow/__snapshots__/ReactionsRow.test.tsx.snap b/packages/shared-components/src/message-body/ReactionsRow/__snapshots__/ReactionsRow.test.tsx.snap
similarity index 100%
rename from packages/shared-components/src/message-body/ReactionRow/__snapshots__/ReactionsRow.test.tsx.snap
rename to packages/shared-components/src/message-body/ReactionsRow/__snapshots__/ReactionsRow.test.tsx.snap
diff --git a/packages/shared-components/src/message-body/ReactionRow/index.tsx b/packages/shared-components/src/message-body/ReactionsRow/index.tsx
similarity index 100%
rename from packages/shared-components/src/message-body/ReactionRow/index.tsx
rename to packages/shared-components/src/message-body/ReactionsRow/index.tsx
diff --git a/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.stories.tsx b/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.stories.tsx
index b3231a5f75..d2409b21d0 100644
--- a/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.stories.tsx
+++ b/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.stories.tsx
@@ -19,9 +19,13 @@ import { withViewDocs } from "../../../.storybook/withViewDocs";
type DisambiguatedProfileProps = DisambiguatedProfileViewSnapshot & DisambiguatedProfileViewActions;
-const DisambiguatedProfileViewWrapperImpl = ({ onClick, ...rest }: DisambiguatedProfileProps): JSX.Element => {
+const DisambiguatedProfileViewWrapperImpl = ({
+ onClick,
+ className,
+ ...rest
+}: DisambiguatedProfileProps & { className?: string }): JSX.Element => {
const vm = useMockedViewModel(rest, { onClick });
- return ;
+ return ;
};
const DisambiguatedProfileViewWrapper = withViewDocs(DisambiguatedProfileViewWrapperImpl, DisambiguatedProfileView);
diff --git a/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.test.tsx b/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.test.tsx
index 0b05a1e0ad..a27b879b52 100644
--- a/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.test.tsx
+++ b/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfile.test.tsx
@@ -209,4 +209,14 @@ describe("DisambiguatedProfileView", () => {
const displayNameElement = screen.getByText("Emphasized User");
expect(displayNameElement).toHaveClass("mx_DisambiguatedProfile_displayName");
});
+
+ it("should apply custom className to the profile container", () => {
+ const vm = new DisambiguatedProfileViewModel({
+ displayName: "Classed User",
+ });
+
+ render();
+ const profileContainer = getProfileContainer("Classed User");
+ expect(profileContainer).toHaveClass("custom-profile", "another-class");
+ });
});
diff --git a/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfileView.tsx b/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfileView.tsx
index 3489541cfd..81fdbac83b 100644
--- a/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfileView.tsx
+++ b/packages/shared-components/src/profile/DisambiguatedProfile/DisambiguatedProfileView.tsx
@@ -24,10 +24,6 @@ export interface DisambiguatedProfileViewSnapshot {
* Undefined if coloring is not enabled.
*/
colorClass?: string;
- /**
- * The CSS class name.
- */
- className?: string;
/**
* The formatted user identifier to display when disambiguation is needed.
* Undefined if disambiguation is not required.
@@ -67,6 +63,10 @@ interface DisambiguatedProfileViewProps {
* The view model for the disambiguated profile.
*/
vm: DisambiguatedProfileViewModel;
+ /**
+ * Optional CSS class name applied to the profile container.
+ */
+ className?: string;
}
/**
@@ -79,8 +79,8 @@ interface DisambiguatedProfileViewProps {
*
* ```
*/
-export function DisambiguatedProfileView({ vm }: Readonly): JSX.Element {
- const { displayName, colorClass, displayIdentifier, title, emphasizeDisplayName, className } = useViewModel(vm);
+export function DisambiguatedProfileView({ vm, className }: Readonly): JSX.Element {
+ const { displayName, colorClass, displayIdentifier, title, emphasizeDisplayName } = useViewModel(vm);
const displayNameClasses = classNames(colorClass, {
[styles.disambiguatedProfile_displayName]: emphasizeDisplayName,
diff --git a/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.stories.tsx b/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.stories.tsx
index d09f04d0a6..b7e14bca05 100644
--- a/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.stories.tsx
+++ b/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.stories.tsx
@@ -20,10 +20,11 @@ const DateSeparatorViewWrapperImpl = ({
onLastMonthPicked,
onBeginningPicked,
onDatePicked,
+ className,
...rest
-}: DateSeparatorProps): JSX.Element => {
+}: DateSeparatorProps & { className?: string }): JSX.Element => {
const vm = useMockedViewModel(rest, { onLastWeekPicked, onLastMonthPicked, onBeginningPicked, onDatePicked });
- return ;
+ return ;
};
const DateSeparatorViewWrapper = withViewDocs(DateSeparatorViewWrapperImpl, DateSeparatorView);
diff --git a/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.tsx b/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.tsx
index a21bed9f82..4d2c21cd96 100644
--- a/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.tsx
+++ b/packages/shared-components/src/timeline/DateSeparatorView/DateSeparatorView.tsx
@@ -29,10 +29,6 @@ export interface DateSeparatorViewSnapshot {
* Reference date as input format used to prefill the jump-to-date picker value.
*/
jumpFromDate?: string;
- /**
- * Extra CSS classes to apply to the component.
- */
- className?: string;
}
export interface DateSeparatorViewActions {
@@ -56,6 +52,10 @@ interface DateSeparatorViewProps {
* The view model for the component.
*/
vm: DateSeparatorViewModel;
+ /**
+ * Extra CSS classes to apply to the component.
+ */
+ className?: string;
}
/**
@@ -68,8 +68,8 @@ interface DateSeparatorViewProps {
*
* ```
*/
-export function DateSeparatorView({ vm }: Readonly): JSX.Element {
- const { label, className, jumpToEnabled } = useViewModel(vm);
+export function DateSeparatorView({ vm, className }: Readonly): JSX.Element {
+ const { label, jumpToEnabled } = useViewModel(vm);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isTriggerHovered, setIsTriggerHovered] = useState(false);
const [isTriggerFocused, setIsTriggerFocused] = useState(false);