diff --git a/apps/web/res/css/views/messages/_common_CryptoEvent.pcss b/apps/web/res/css/views/messages/_common_CryptoEvent.pcss index affbd26d64..06b7ccc1c7 100644 --- a/apps/web/res/css/views/messages/_common_CryptoEvent.pcss +++ b/apps/web/res/css/views/messages/_common_CryptoEvent.pcss @@ -9,29 +9,7 @@ Please see LICENSE files in the repository root for full details. .mx_EventTileBubble.mx_cryptoEvent { margin: var(--EventTileBubble_margin-block) auto; - &.mx_cryptoEvent_icon svg { + svg[data-state="supported"] { color: $header-panel-text-primary-color; } - - .mx_cryptoEvent_state, - .mx_cryptoEvent_buttons { - grid-column: 3; - grid-row: 1 / 3; - } - - .mx_cryptoEvent_buttons { - align-items: center; - display: flex; - gap: 5px; - } - - .mx_cryptoEvent_state { - width: 130px; - padding: 10px 20px; - margin: auto 0; - text-align: center; - color: $tertiary-content; - overflow-wrap: break-word; - font-size: $font-12px; - } } diff --git a/apps/web/src/components/structures/MessagePanel.tsx b/apps/web/src/components/structures/MessagePanel.tsx index 207b91b0b6..2b0ebeacf2 100644 --- a/apps/web/src/components/structures/MessagePanel.tsx +++ b/apps/web/src/components/structures/MessagePanel.tsx @@ -66,7 +66,7 @@ const continuedTypes = [EventType.Sticker, EventType.RoomMessage]; */ function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): JSX.Element { const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts })); - return ; + return ; } /** diff --git a/apps/web/src/components/structures/RoomView.tsx b/apps/web/src/components/structures/RoomView.tsx index a8050571b2..18375a9580 100644 --- a/apps/web/src/components/structures/RoomView.tsx +++ b/apps/web/src/components/structures/RoomView.tsx @@ -416,7 +416,8 @@ function RoomStatusBarWrappedView(props: ConstructorParameters new EncryptionEventViewModel({ mxEvent, cli })); - return ; + + return ; } export class RoomView extends React.Component { diff --git a/apps/web/src/components/structures/WaitingForThirdPartyRoomView.tsx b/apps/web/src/components/structures/WaitingForThirdPartyRoomView.tsx index 0b0e07bbeb..00228ed9a0 100644 --- a/apps/web/src/components/structures/WaitingForThirdPartyRoomView.tsx +++ b/apps/web/src/components/structures/WaitingForThirdPartyRoomView.tsx @@ -45,7 +45,7 @@ export const WaitingForThirdPartyRoomView: React.FC = ({ roomView, resize } - className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon" + className="mx_EventTileBubble mx_cryptoEvent" title={_t("room|waiting_for_join_title", { brand })} subtitle={_t("room|waiting_for_join_subtitle", { brand })} /> diff --git a/apps/web/src/components/structures/grouper/CreationGrouper.tsx b/apps/web/src/components/structures/grouper/CreationGrouper.tsx index eafd7ade1d..9608f18b0f 100644 --- a/apps/web/src/components/structures/grouper/CreationGrouper.tsx +++ b/apps/web/src/components/structures/grouper/CreationGrouper.tsx @@ -29,7 +29,7 @@ import { DateSeparatorViewModel } from "../../../viewmodels/timeline/DateSeparat */ function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): ReactNode { const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts })); - return ; + return ; } export class CreationGrouper extends BaseGrouper { diff --git a/apps/web/src/components/structures/grouper/MainGrouper.tsx b/apps/web/src/components/structures/grouper/MainGrouper.tsx index bf9a178fc8..f7a0eb4d94 100644 --- a/apps/web/src/components/structures/grouper/MainGrouper.tsx +++ b/apps/web/src/components/structures/grouper/MainGrouper.tsx @@ -31,7 +31,7 @@ const groupedStateEvents = [ */ function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): ReactNode { const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts })); - return ; + return ; } // Wrap consecutive grouped events in a ListSummary diff --git a/apps/web/src/components/views/dialogs/MessageEditHistoryDialog.tsx b/apps/web/src/components/views/dialogs/MessageEditHistoryDialog.tsx index 599d2c32f0..950c77c432 100644 --- a/apps/web/src/components/views/dialogs/MessageEditHistoryDialog.tsx +++ b/apps/web/src/components/views/dialogs/MessageEditHistoryDialog.tsx @@ -143,7 +143,10 @@ export default class MessageEditHistoryDialog extends React.PureComponent - + , ); } diff --git a/apps/web/src/components/views/elements/ImageView.tsx b/apps/web/src/components/views/elements/ImageView.tsx index 983cd42575..6c72c8b276 100644 --- a/apps/web/src/components/views/elements/ImageView.tsx +++ b/apps/web/src/components/views/elements/ImageView.tsx @@ -657,5 +657,5 @@ function MessageTimestampWrapper(props: MessageTimestampViewModelProps): JSX.Ele vm.setHref(props.href); vm.setHandlers({ onClick: props.onClick }); }, [vm, props]); - return ; + return ; } diff --git a/apps/web/src/components/views/messages/MKeyVerificationRequest.tsx b/apps/web/src/components/views/messages/MKeyVerificationRequest.tsx index 022766868e..6765f330da 100644 --- a/apps/web/src/components/views/messages/MKeyVerificationRequest.tsx +++ b/apps/web/src/components/views/messages/MKeyVerificationRequest.tsx @@ -75,7 +75,7 @@ const MKeyVerificationRequest: React.FC = ({ mxEvent, timestamp }) => { return ( } - className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon" + className="mx_EventTileBubble mx_cryptoEvent" title={title} subtitle={subtitle} > diff --git a/apps/web/src/components/views/messages/MessageEvent.tsx b/apps/web/src/components/views/messages/MessageEvent.tsx index 6bb28fd188..961e090d13 100644 --- a/apps/web/src/components/views/messages/MessageEvent.tsx +++ b/apps/web/src/components/views/messages/MessageEvent.tsx @@ -347,5 +347,5 @@ function DecryptionFailureBodyWrapper({ mxEvent, ref }: IBodyProps): JSX.Element useEffect(() => { vm.setVerificationState(verificationState); }, [verificationState, vm]); - return ; + return ; } diff --git a/apps/web/src/components/views/messages/SenderProfile.tsx b/apps/web/src/components/views/messages/SenderProfile.tsx index 89b483f735..a68083009f 100644 --- a/apps/web/src/components/views/messages/SenderProfile.tsx +++ b/apps/web/src/components/views/messages/SenderProfile.tsx @@ -37,7 +37,6 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps) colored: true, emphasizeDisplayName: true, withTooltip, - className: "mx_DisambiguatedProfile", }), ); @@ -45,7 +44,7 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps) disambiguatedProfileVM.setMember(sender ?? "", member); }, [disambiguatedProfileVM, member, sender]); return mxEvent.getContent().msgtype !== MsgType.Emote ? ( - + ) : ( <> ); diff --git a/apps/web/src/components/views/rooms/EventTile.tsx b/apps/web/src/components/views/rooms/EventTile.tsx index a6b7b7c326..af35f2c800 100644 --- a/apps/web/src/components/views/rooms/EventTile.tsx +++ b/apps/web/src/components/views/rooms/EventTile.tsx @@ -1617,7 +1617,7 @@ function DecryptionFailureBodyWrapper({ mxEvent }: { mxEvent: MatrixEvent }): JS vm.setVerificationState(verificationState); }, [verificationState, vm]); - return ; + return ; } /** @@ -1643,7 +1643,7 @@ function MessageTimestampWrapper(props: MessageTimestampViewModelProps): JSX.Ele {props.receivedTs ? ( ) : undefined} - + ); } @@ -1859,10 +1859,6 @@ function ReactionsRowWrapper({ mxEvent, reactions }: Readonly { - vm.setChildren(items); - }, [items, vm]); - if (!snapshot.isVisible || !items?.length) { return null; } @@ -1878,7 +1874,9 @@ function ReactionsRowWrapper({ mxEvent, reactions }: Readonly - + + {items} + {contextMenu} ); diff --git a/apps/web/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx b/apps/web/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx index f8dbd02829..e298943e3a 100644 --- a/apps/web/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx +++ b/apps/web/src/components/views/rooms/MemberList/tiles/RoomMemberTileView.tsx @@ -53,13 +53,12 @@ export function RoomMemberTileView(props: IProps): JSX.Element { fallbackName: name, member, withTooltip: true, - className: "mx_DisambiguatedProfile", }), ); useEffect(() => { disambiguatedProfileVM.setMember(name, member); }, [disambiguatedProfileVM, member, name]); - const nameJSX = ; + const nameJSX = ; const presenceState = member.presenceState; let presenceJSX: JSX.Element | undefined; diff --git a/apps/web/src/components/views/rooms/SearchResultTile.tsx b/apps/web/src/components/views/rooms/SearchResultTile.tsx index 1b45dbf024..8e4e7e4f3e 100644 --- a/apps/web/src/components/views/rooms/SearchResultTile.tsx +++ b/apps/web/src/components/views/rooms/SearchResultTile.tsx @@ -40,7 +40,7 @@ interface IProps { */ function DateSeparatorWrapper({ roomId, ts }: { roomId: string; ts: number }): JSX.Element { const vm = useCreateAutoDisposedViewModel(() => new DateSeparatorViewModel({ roomId, ts })); - return ; + return ; } export default class SearchResultTile extends React.Component { diff --git a/apps/web/src/events/EventTileFactory.tsx b/apps/web/src/events/EventTileFactory.tsx index 41286c5075..fb637fec30 100644 --- a/apps/web/src/events/EventTileFactory.tsx +++ b/apps/web/src/events/EventTileFactory.tsx @@ -88,7 +88,8 @@ export const TextualEventFactory: Factory = (ref, props) => { function EncryptionEventWrappedView({ mxEvent, ref }: IBodyProps): JSX.Element { const cli = useMatrixClientContext(); const vm = useCreateAutoDisposedViewModel(() => new EncryptionEventViewModel({ mxEvent, cli })); - return ; + + return ; } const EncryptionEventFactory: Factory = (ref, props) => { return ; diff --git a/apps/web/src/utils/exportUtils/HtmlExport.tsx b/apps/web/src/utils/exportUtils/HtmlExport.tsx index 4ae8e231d4..ce8f3fd14f 100644 --- a/apps/web/src/utils/exportUtils/HtmlExport.tsx +++ b/apps/web/src/utils/exportUtils/HtmlExport.tsx @@ -266,7 +266,7 @@ export default class HTMLExporter extends Exporter { try { const dateSeparator = (
  • - +
  • ); return this.renderToStaticMarkupWithProviders(dateSeparator); diff --git a/apps/web/src/viewmodels/event-tiles/EncryptionEventViewModel.ts b/apps/web/src/viewmodels/event-tiles/EncryptionEventViewModel.ts index 03a840b621..6084aef56b 100644 --- a/apps/web/src/viewmodels/event-tiles/EncryptionEventViewModel.ts +++ b/apps/web/src/viewmodels/event-tiles/EncryptionEventViewModel.ts @@ -78,13 +78,11 @@ export class EncryptionEventViewModel props: EncryptionEventViewModelProps, isEncrypted: boolean, ): EncryptionEventViewSnapshotInterface { - // Keep legacy class names for compatibility with existing timeline layout and styling. const newSnapshot: EncryptionEventViewSnapshotInterface = { state: EncryptionEventState.CHANGED, encryptedStateEvents: undefined, userName: undefined, timestamp: props.timestamp, - className: "mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon", }; const content = props.mxEvent.getContent(); @@ -113,8 +111,6 @@ export class EncryptionEventViewModel newSnapshot.state = EncryptionEventState.DISABLE_ATTEMPT; } else { newSnapshot.state = EncryptionEventState.UNSUPPORTED; - // Unsupported branch matches legacy EncryptionEvent class usage (no icon class). - newSnapshot.className = "mx_EventTileBubble mx_cryptoEvent"; } return newSnapshot; diff --git a/apps/web/src/viewmodels/message-body/DecryptionFailureBodyViewModel.ts b/apps/web/src/viewmodels/message-body/DecryptionFailureBodyViewModel.ts index 965c4a0942..a51ea71117 100644 --- a/apps/web/src/viewmodels/message-body/DecryptionFailureBodyViewModel.ts +++ b/apps/web/src/viewmodels/message-body/DecryptionFailureBodyViewModel.ts @@ -22,10 +22,6 @@ export interface DecryptionFailureBodyViewModelProps { * The local device verification state. */ verificationState?: boolean; - /** - * Extra CSS classes to apply to the component - */ - extraClassNames?: string[]; } /** @@ -62,30 +58,21 @@ export class DecryptionFailureBodyViewModel /** * @param decryptionFailureCode - The decryption failure code for the event. * @param verificationState - The local device verification state. - * @param extraClassNames - Extra CSS classes to apply to the component. */ private static readonly computeSnapshot = ( decryptionFailureCode: DecryptionFailureCode | null, verificationState?: boolean, - extraClassNames?: string[], ): DecryptionFailureBodyViewSnapshotInterface => { - // Keep mx_DecryptionFailureBody and mx_EventTile_content to support the compatibility with existing timeline and the all the layout - const defaultClassNames = ["mx_DecryptionFailureBody", "mx_EventTile_content"]; return { decryptionFailureReason: DecryptionFailureBodyViewModel.getDecryptionReasonFromCode(decryptionFailureCode), isLocalDeviceVerified: verificationState, - extraClassNames: extraClassNames ? defaultClassNames.concat(extraClassNames) : defaultClassNames, }; }; public constructor(props: DecryptionFailureBodyViewModelProps) { super( props, - DecryptionFailureBodyViewModel.computeSnapshot( - props.decryptionFailureCode, - props.verificationState, - props.extraClassNames, - ), + DecryptionFailureBodyViewModel.computeSnapshot(props.decryptionFailureCode, props.verificationState), ); } diff --git a/apps/web/src/viewmodels/message-body/MessageTimestampViewModel.ts b/apps/web/src/viewmodels/message-body/MessageTimestampViewModel.ts index 8d9299ba88..da4295176c 100644 --- a/apps/web/src/viewmodels/message-body/MessageTimestampViewModel.ts +++ b/apps/web/src/viewmodels/message-body/MessageTimestampViewModel.ts @@ -91,14 +91,12 @@ export class MessageTimestampViewModel receivedAt = formatFullDate(receivedDate, props.showTwelveHour); } - // Keep mx_MessageTimestamp for compatibility with the existing timeline and layout. return { ts: timestamp, tsSentAt: sentAt, tsReceivedAt: receivedAt, inhibitTooltip: props.inhibitTooltip, href: props.href, - className: "mx_MessageTimestamp", }; }; diff --git a/apps/web/src/viewmodels/message-body/ReactionsRowViewModel.ts b/apps/web/src/viewmodels/message-body/ReactionsRowViewModel.ts index 48443b2ea2..3842b25c36 100644 --- a/apps/web/src/viewmodels/message-body/ReactionsRowViewModel.ts +++ b/apps/web/src/viewmodels/message-body/ReactionsRowViewModel.ts @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -import { type MouseEvent, type MouseEventHandler, type ReactNode } from "react"; +import { type MouseEvent, type MouseEventHandler } from "react"; import { BaseViewModel, type ReactionsRowViewSnapshot, @@ -41,10 +41,6 @@ export interface ReactionsRowViewModelProps { * Optional callback invoked on add-reaction button context-menu. */ onAddReactionContextMenu?: MouseEventHandler; - /** - * Reaction row children (typically reaction buttons). - */ - children?: ReactNode; } interface InternalProps extends ReactionsRowViewModelProps { @@ -59,18 +55,16 @@ export class ReactionsRowViewModel props: InternalProps, ): Pick< ReactionsRowViewSnapshot, - "isVisible" | "showAllButtonVisible" | "showAddReactionButton" | "addReactionButtonActive" | "children" + "isVisible" | "showAllButtonVisible" | "showAddReactionButton" | "addReactionButtonActive" > => ({ isVisible: props.isActionable && props.reactionGroupCount > 0, showAllButtonVisible: props.reactionGroupCount > MAX_ITEMS_WHEN_LIMITED + 1 && !props.showAll, showAddReactionButton: props.canReact, addReactionButtonActive: !!props.addReactionButtonActive, - children: props.children, }); private static readonly computeSnapshot = (props: InternalProps): ReactionsRowViewSnapshot => ({ ariaLabel: _t("common|reactions"), - className: "mx_ReactionsRow", showAllButtonLabel: _t("action|show_all"), addReactionButtonLabel: _t("timeline|reactions|add_reaction_prompt"), addReactionButtonVisible: false, @@ -136,15 +130,6 @@ export class ReactionsRowViewModel this.snapshot.merge({ addReactionButtonActive }); } - public setChildren(children?: ReactNode): void { - this.props = { - ...this.props, - children, - }; - - this.snapshot.merge({ children }); - } - public setAddReactionHandlers({ onAddReactionClick, onAddReactionContextMenu, diff --git a/apps/web/src/viewmodels/profile/DisambiguatedProfileViewModel.ts b/apps/web/src/viewmodels/profile/DisambiguatedProfileViewModel.ts index 49a2db4e29..0617370038 100644 --- a/apps/web/src/viewmodels/profile/DisambiguatedProfileViewModel.ts +++ b/apps/web/src/viewmodels/profile/DisambiguatedProfileViewModel.ts @@ -66,10 +66,6 @@ export interface DisambiguatedProfileViewModelProps { * Optional click handler for the profile. */ onClick?: DisambiguatedProfileViewActions["onClick"]; - /** - * Optional CSS class name to apply to the profile. - */ - className?: string; } /** @@ -83,7 +79,7 @@ export class DisambiguatedProfileViewModel private static readonly computeSnapshot = ( props: DisambiguatedProfileViewModelProps, ): DisambiguatedProfileViewSnapshot => { - const { member, fallbackName, colored, emphasizeDisplayName, withTooltip, className } = props; + const { member, fallbackName, colored, emphasizeDisplayName, withTooltip } = props; // Compute display name const displayName = member?.rawDisplayName || fallbackName; @@ -123,7 +119,6 @@ export class DisambiguatedProfileViewModel return { displayName, colorClass, - className, displayIdentifier, title, emphasizeDisplayName, diff --git a/apps/web/src/viewmodels/timeline/DateSeparatorViewModel.tsx b/apps/web/src/viewmodels/timeline/DateSeparatorViewModel.tsx index 2221d795a1..320e79e48f 100644 --- a/apps/web/src/viewmodels/timeline/DateSeparatorViewModel.tsx +++ b/apps/web/src/viewmodels/timeline/DateSeparatorViewModel.tsx @@ -68,7 +68,6 @@ export class DateSeparatorViewModel super(props, { label: DateSeparatorViewModel.computeLabel(props, relativeDatesEnabled), - className: "mx_TimelineSeparator", }); this.relativeDatesEnabled = relativeDatesEnabled; @@ -101,7 +100,6 @@ export class DateSeparatorViewModel const label = DateSeparatorViewModel.computeLabel(this.props, this.relativeDatesEnabled); return { label, - className: "mx_TimelineSeparator", jumpToEnabled: this.jumpToDateEnabled && !this.props.forExport, jumpFromDate: formatDateForInput(new Date(this.props.ts)), }; diff --git a/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap index dd83b7d0c5..828ea77ed2 100644 --- a/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -717,9 +717,10 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t style="height: 400px;" >
    { await waitFor(() => expect(vm.getSnapshot().state).toBe(EncryptionEventState.ENABLED)); expect(vm.getSnapshot()).toMatchObject({ state: EncryptionEventState.ENABLED, - className: "mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon", encryptedStateEvents: false, }); }); @@ -125,7 +124,6 @@ describe("EncryptionEventViewModel", () => { const vm = createVm(); await waitFor(() => expect(vm.getSnapshot().state).toBe(EncryptionEventState.UNSUPPORTED)); - expect(vm.getSnapshot().className).toBe("mx_EventTileBubble mx_cryptoEvent"); }); it("sets ENABLED_DM with partner display name", async () => { diff --git a/apps/web/test/viewmodels/message-body/DecryptionFailureBodyViewModel-test.tsx b/apps/web/test/viewmodels/message-body/DecryptionFailureBodyViewModel-test.tsx index 85580842de..580dea3a31 100644 --- a/apps/web/test/viewmodels/message-body/DecryptionFailureBodyViewModel-test.tsx +++ b/apps/web/test/viewmodels/message-body/DecryptionFailureBodyViewModel-test.tsx @@ -22,19 +22,6 @@ describe("DecryptionFailureBodyViewModel", () => { }); }); - it("should return the snapshot with extra class names", () => { - const vm = new DecryptionFailureBodyViewModel({ - decryptionFailureCode: null, - verificationState: true, - extraClassNames: ["custom-class"], - }); - expect(vm.getSnapshot()).toMatchObject({ - decryptionFailureReason: DecryptionFailureReason.UNABLE_TO_DECRYPT, - isLocalDeviceVerified: true, - extraClassNames: ["mx_DecryptionFailureBody", "mx_EventTile_content", "custom-class"], - }); - }); - it.each([ { code: DecryptionFailureCode.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED, diff --git a/apps/web/test/viewmodels/message-body/MessageTimestampViewModel-test.tsx b/apps/web/test/viewmodels/message-body/MessageTimestampViewModel-test.tsx index 864d4ce622..08039003d0 100644 --- a/apps/web/test/viewmodels/message-body/MessageTimestampViewModel-test.tsx +++ b/apps/web/test/viewmodels/message-body/MessageTimestampViewModel-test.tsx @@ -42,14 +42,13 @@ describe("MessageTimestampViewModel", () => { }); }); - it("should return the snapshot with extra class names", () => { + it("should return the snapshot without presentation class names", () => { const vm = new MessageTimestampViewModel({ ts: nowDate.getTime(), }); expect(vm.getSnapshot()).toMatchObject({ ts: "08:09", tsSentAt: "Fri, Dec 17, 2021, 08:09:00", - className: "mx_MessageTimestamp", }); }); diff --git a/apps/web/test/viewmodels/message-body/ReactionsRowViewModel-test.tsx b/apps/web/test/viewmodels/message-body/ReactionsRowViewModel-test.tsx index 46c57d2b21..30cc4be2e7 100644 --- a/apps/web/test/viewmodels/message-body/ReactionsRowViewModel-test.tsx +++ b/apps/web/test/viewmodels/message-body/ReactionsRowViewModel-test.tsx @@ -29,7 +29,6 @@ describe("ReactionsRowViewModel", () => { expect(snapshot.showAllButtonVisible).toBe(true); expect(snapshot.showAddReactionButton).toBe(true); expect(snapshot.addReactionButtonActive).toBe(false); - expect(snapshot.className).toContain("mx_ReactionsRow"); }); it("hides show-all after onShowAllClick", () => { diff --git a/apps/web/test/viewmodels/profile/DisambiguatedProfileViewModel-test.tsx b/apps/web/test/viewmodels/profile/DisambiguatedProfileViewModel-test.tsx index faf2edecfd..d92837e120 100644 --- a/apps/web/test/viewmodels/profile/DisambiguatedProfileViewModel-test.tsx +++ b/apps/web/test/viewmodels/profile/DisambiguatedProfileViewModel-test.tsx @@ -31,7 +31,6 @@ describe("DisambiguatedProfileViewModel", () => { expect(vm.getSnapshot()).toEqual({ displayName: "Alice", colorClass: "mx_Username_color3", - className: undefined, displayIdentifier: "@alice:example.org", title: "Alice (@alice:example.org)", emphasizeDisplayName: true, @@ -47,23 +46,12 @@ describe("DisambiguatedProfileViewModel", () => { expect(vm.getSnapshot()).toMatchObject({ displayName: "Fallback", colorClass: undefined, - className: undefined, displayIdentifier: undefined, title: undefined, emphasizeDisplayName: undefined, }); }); - it("should pass through className prop", () => { - const vm = new DisambiguatedProfileViewModel({ - member, - fallbackName: "Fallback", - className: "mx_DisambiguatedProfile", - }); - - expect(vm.getSnapshot().className).toBe("mx_DisambiguatedProfile"); - }); - it("should delegate onClick without emitting a snapshot update", () => { const onClick = jest.fn(); const vm = new DisambiguatedProfileViewModel({ diff --git a/apps/web/test/viewmodels/timeline/DateSeparatorViewModel-test.tsx b/apps/web/test/viewmodels/timeline/DateSeparatorViewModel-test.tsx index 9abfd76e6e..e3fad0c578 100644 --- a/apps/web/test/viewmodels/timeline/DateSeparatorViewModel-test.tsx +++ b/apps/web/test/viewmodels/timeline/DateSeparatorViewModel-test.tsx @@ -109,7 +109,6 @@ describe("DateSeparatorViewModel", () => { const vm = createViewModel(); expect(vm.getSnapshot().label).toBe("today"); - expect(vm.getSnapshot().className).toBe("mx_TimelineSeparator"); }); it("uses full date when exporting", () => { diff --git a/packages/shared-components/__vis__/linux/__baselines__/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.stories.tsx/has-extra-class-names-auto.png b/packages/shared-components/__vis__/linux/__baselines__/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.stories.tsx/has-extra-class-names-auto.png deleted file mode 100644 index 3e5ba0fdb5..0000000000 Binary files a/packages/shared-components/__vis__/linux/__baselines__/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.stories.tsx/has-extra-class-names-auto.png and /dev/null differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/message-body/MessageTimestampView/MessageTimestampView.stories.tsx/has-extra-class-names-auto.png b/packages/shared-components/__vis__/linux/__baselines__/message-body/MessageTimestampView/MessageTimestampView.stories.tsx/has-extra-class-names-auto.png deleted file mode 100644 index 9b784175af..0000000000 Binary files a/packages/shared-components/__vis__/linux/__baselines__/message-body/MessageTimestampView/MessageTimestampView.stories.tsx/has-extra-class-names-auto.png and /dev/null differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/add-reaction-button-active-auto.png b/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/add-reaction-button-active-auto.png similarity index 100% rename from packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/add-reaction-button-active-auto.png rename to packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/add-reaction-button-active-auto.png diff --git a/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/add-reaction-button-hidden-until-hover-auto.png b/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/add-reaction-button-hidden-until-hover-auto.png similarity index 100% rename from packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/add-reaction-button-hidden-until-hover-auto.png rename to packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/add-reaction-button-hidden-until-hover-auto.png diff --git a/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/default-auto.png b/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/default-auto.png similarity index 100% rename from packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/default-auto.png rename to packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/default-auto.png diff --git a/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/hidden-auto.png b/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/hidden-auto.png similarity index 100% rename from packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/hidden-auto.png rename to packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/hidden-auto.png diff --git a/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/with-show-all-button-auto.png b/packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/with-show-all-button-auto.png similarity index 100% rename from packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionRow/ReactionsRow.stories.tsx/with-show-all-button-auto.png rename to packages/shared-components/__vis__/linux/__baselines__/message-body/ReactionsRow/ReactionsRow.stories.tsx/with-show-all-button-auto.png diff --git a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.module.css b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.module.css index a49c7f1b05..32161e4df9 100644 --- a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.module.css +++ b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.module.css @@ -5,6 +5,10 @@ * Please see LICENSE files in the repository root for full details. */ -.error { +.content svg[data-state="supported"] { + color: inherit; +} + +.content svg[data-state="unsupported"] { color: var(--cpd-color-icon-critical-primary); } diff --git a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.stories.tsx b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.stories.tsx index 8a41e47adf..83d472dcb6 100644 --- a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.stories.tsx +++ b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.stories.tsx @@ -14,10 +14,13 @@ import { withViewDocs } from "../../../.storybook/withViewDocs"; type EncryptionEventProps = EncryptionEventViewSnapshot; -const EncryptionEventViewWrapperImpl = ({ ...rest }: EncryptionEventProps): JSX.Element => { +const EncryptionEventViewWrapperImpl = ({ + className, + ...rest +}: EncryptionEventProps & { className?: string }): JSX.Element => { const vm = useMockedViewModel(rest, {}); - return ; + return ; }; const EncryptionEventViewWrapper = withViewDocs(EncryptionEventViewWrapperImpl, EncryptionEventView); diff --git a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.test.tsx b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.test.tsx index 214890b8ee..f71b07f99b 100644 --- a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.test.tsx +++ b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.test.tsx @@ -36,9 +36,8 @@ describe("EncryptionEventView", () => { state, encryptedStateEvents, userName, - className, }); - render(); + render(); }; it("renders Default story", () => { diff --git a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.tsx b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.tsx index 218ae74da6..afa7eb8356 100644 --- a/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.tsx +++ b/packages/shared-components/src/event-tiles/EncryptionEventView/EncryptionEventView.tsx @@ -7,6 +7,7 @@ import React, { type JSX } from "react"; import { LockSolidIcon, ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; +import classNames from "classnames"; import { type ViewModel, useViewModel } from "../../viewmodel"; import styles from "./EncryptionEventView.module.css"; @@ -35,8 +36,6 @@ export type EncryptionEventViewSnapshot = { encryptedStateEvents?: boolean; /** Display name for DM partner, used by ENABLED_DM subtitle text. */ userName?: string; - /** Optional CSS classes passed through to EventTileBubble. */ - className?: string; /** Optional timestamp element rendered in the EventTileBubble footer slot. */ timestamp?: JSX.Element; }; @@ -52,16 +51,35 @@ export interface EncryptionEventViewProps { */ vm: ViewModel; /** - * Ref forwarded to the root DOM element. + * Optional CSS classes passed through to EventTileBubble. + */ + className?: string; + /** + * Optional Ref forwarded to the root DOM element. */ ref?: React.RefObject; } -export function EncryptionEventView({ vm, ref }: Readonly): JSX.Element { +/** + * Renders a timeline bubble describing an encryption-related room event. + * + * Text and icon are selected from `snapshot.state` with optional context: + * - `encryptedStateEvents` switches to state-event specific wording. + * - `userName` is used for DM-specific subtitle text. + * - `timestamp` renders in the bubble footer slot. + * + * Use `className` for host-level styling, following the default React pattern. + * + * @example + * ```tsx + * + * ``` + */ +export function EncryptionEventView({ vm, ref, className }: Readonly): JSX.Element { const { translate: _t } = useI18n(); - const { state, encryptedStateEvents, userName, className, timestamp } = useViewModel(vm); + const { state, encryptedStateEvents, userName, timestamp } = useViewModel(vm); - let icon = ; + let icon = ; let title = encryptedStateEvents ? _t("common|state_encryption_enabled") : _t("common|encryption_enabled"); let subtitle = ""; @@ -86,14 +104,20 @@ export function EncryptionEventView({ vm, ref }: Readonly; + icon = ; title = _t("timeline|m.room.encryption|disabled"); subtitle = _t("timeline|m.room.encryption|unsupported"); break; } return ( - + {timestamp} ); diff --git a/packages/shared-components/src/event-tiles/EncryptionEventView/__snapshots__/EncryptionEventView.test.tsx.snap b/packages/shared-components/src/event-tiles/EncryptionEventView/__snapshots__/EncryptionEventView.test.tsx.snap index 09e39927bb..5b67edede9 100644 --- a/packages/shared-components/src/event-tiles/EncryptionEventView/__snapshots__/EncryptionEventView.test.tsx.snap +++ b/packages/shared-components/src/event-tiles/EncryptionEventView/__snapshots__/EncryptionEventView.test.tsx.snap @@ -3,9 +3,10 @@ exports[`EncryptionEventView > renders Default story 1`] = `
    renders Default story 1`] = ` exports[`EncryptionEventView > renders DisableAttempt story 1`] = `
    renders DisableAttempt story 1`] = ` exports[`EncryptionEventView > renders EnabledDirectMessage story 1`] = `
    renders EnabledDirectMessage story 1`] = ` exports[`EncryptionEventView > renders EnabledLocalRoom story 1`] = `
    renders EnabledLocalRoom story 1`] = ` exports[`EncryptionEventView > renders ParametersChanged story 1`] = `
    renders ParametersChanged story 1`] = ` exports[`EncryptionEventView > renders StateEncryptionEnabled story 1`] = `
    renders StateEncryptionEnabled story 1`] = ` exports[`EncryptionEventView > renders Unsupported story 1`] = `
    renders Unsupported story 1`] = ` exports[`EncryptionEventView > renders WithTimestamp story 1`] = `
    { +const DecryptionFailureBodyViewWrapperImpl = ({ + className, + ...rest +}: DecryptionFailureBodyProps & { className?: string }): JSX.Element => { const vm = useMockedViewModel(rest, {}); - return ; + return ; }; const DecryptionFailureBodyViewWrapper = withViewDocs(DecryptionFailureBodyViewWrapperImpl, DecryptionFailureBodyView); @@ -40,7 +43,7 @@ const meta = { args: { decryptionFailureReason: DecryptionFailureReason.UNABLE_TO_DECRYPT, isLocalDeviceVerified: true, - extraClassNames: ["extra_class"], + className: "extra_class", }, } satisfies Meta; @@ -49,24 +52,17 @@ type Story = StoryObj; export const Default: Story = {}; -export const HasExtraClassNames: Story = { - args: { - decryptionFailureReason: DecryptionFailureReason.UNABLE_TO_DECRYPT, - extraClassNames: ["extra_class_1", "extra_class_2"], - }, -}; - export const HasErrorClassName: Story = { args: { decryptionFailureReason: DecryptionFailureReason.UNSIGNED_SENDER_DEVICE, - extraClassNames: undefined, + className: undefined, }, }; export const HasErrorBlockIcon: Story = { args: { decryptionFailureReason: DecryptionFailureReason.SENDER_IDENTITY_PREVIOUSLY_VERIFIED, - extraClassNames: undefined, + className: undefined, }, }; @@ -74,7 +70,7 @@ export const HasBackupConfiguredVerifiedFalse: Story = { args: { decryptionFailureReason: DecryptionFailureReason.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED, isLocalDeviceVerified: false, - extraClassNames: undefined, + className: undefined, }, }; @@ -82,6 +78,6 @@ export const HasBackupConfiguredVerifiedTrue: Story = { args: { decryptionFailureReason: DecryptionFailureReason.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED, isLocalDeviceVerified: true, - extraClassNames: undefined, + className: undefined, }, }; diff --git a/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.test.tsx b/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.test.tsx index b886584c35..954f910550 100644 --- a/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.test.tsx +++ b/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.test.tsx @@ -5,26 +5,23 @@ * Please see LICENSE files in the repository root for full details. */ -import { composeStories } from "@storybook/react-vite"; import { render } from "@test-utils"; import React from "react"; import { describe, it, expect } from "vitest"; import { DecryptionFailureBodyView, DecryptionFailureReason } from "./DecryptionFailureBodyView"; import { MockViewModel } from "../../viewmodel"; -import * as stories from "./DecryptionFailureBodyView.stories"; - -const { HasExtraClassNames } = composeStories(stories); describe("DecryptionFailureBodyView", () => { function customRender( decryptionFailureReason: DecryptionFailureReason, isLocalDeviceVerified: boolean = false, - extraClassNames: string[] | undefined = undefined, + className: string | undefined = undefined, ): ReturnType { return render( , ); } @@ -38,12 +35,14 @@ describe("DecryptionFailureBodyView", () => { ); } - it("Should display with extra class names", () => { - // When - const { container } = render(); + it("applies custom className to the root element", () => { + const { container } = customRender( + DecryptionFailureReason.UNABLE_TO_DECRYPT, + false, + "extra_class_1 extra_class_2", + ); - // Then - expect(container).toMatchSnapshot(); + expect(container.firstChild).toHaveClass("extra_class_1", "extra_class_2"); }); it.each([true, false])(`Should display "Unable to decrypt message and device verification is %s"`, (verified) => { diff --git a/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.tsx b/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.tsx index 8ea36dc1b6..a3f99e1ac6 100644 --- a/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.tsx +++ b/packages/shared-components/src/message-body/DecryptionFailureBodyView/DecryptionFailureBodyView.tsx @@ -65,14 +65,14 @@ export interface DecryptionFailureBodyViewSnapshot { * The local device verification state. */ isLocalDeviceVerified?: boolean; - /** - * Extra CSS classes to apply to the component - */ - extraClassNames?: string[]; } /** * The view model for the component. + * + * Snapshot data is intentionally content-focused (`decryptionFailureReason` + * plus optional `isLocalDeviceVerified`). Container styling is supplied + * via component props. */ export type DecryptionFailureBodyViewModel = ViewModel; @@ -81,6 +81,10 @@ interface DecryptionFailureBodyViewProps { * The view model for the component. */ vm: DecryptionFailureBodyViewModel; + /** + * Optional CSS class names to apply to the component container. + */ + className?: string; /** * React ref to attach to any React components returned */ @@ -156,17 +160,27 @@ function errorClassName(decryptionFailureReason: DecryptionFailureReason): strin } /** - * A placeholder element for messages that could not be decrypted + * Renders a message-body placeholder for events that could not be decrypted. + * + * Message copy and warning styling are derived from snapshot values: + * - `decryptionFailureReason` selects the base text/variant. + * - `isLocalDeviceVerified` influences historical-backup messaging. + * + * Use `className` for host-level container styling, following standard React patterns. * * @example * ```tsx - * + * * ``` */ -export function DecryptionFailureBodyView({ vm, ref }: Readonly): JSX.Element { +export function DecryptionFailureBodyView({ + vm, + ref, + className, +}: Readonly): JSX.Element { const i18nApi = useI18n(); - const { decryptionFailureReason, isLocalDeviceVerified, extraClassNames } = useViewModel(vm); - const classes = classNames(styles.content, errorClassName(decryptionFailureReason), extraClassNames); + const { decryptionFailureReason, isLocalDeviceVerified } = useViewModel(vm); + const classes = classNames(styles.content, errorClassName(decryptionFailureReason), className); return (
    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`] = ` -
    -
    - Unable to decrypt message -
    -
    -`; - exports[`DecryptionFailureBodyView > should handle historical messages with no key backup and device verification is false 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`] = ` -
    - - 04:58 - -
    -`; - 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);