Skip to content

feat: Expandable Message Composer#36920

Merged
kodiakhq[bot] merged 13 commits intodevelopfrom
feat/expand-composer
Oct 14, 2025
Merged

feat: Expandable Message Composer#36920
kodiakhq[bot] merged 13 commits intodevelopfrom
feat/expand-composer

Conversation

@MartinSchoeler
Copy link
Member

@MartinSchoeler MartinSchoeler commented Sep 11, 2025

Proposed changes (including videos or screenshots)

This PR should be a proposal for this feature.
Screenshot 2025-10-07 at 18 05 34

Screen.Recording.2025-09-11.at.14.55.02.mov

Why add this?

Given the way our current composer height limitations, sometimes larger messages are difficult to write and edit, due the need to keep scrolling up and down. While having the composer always grow to a large height is not ideal, and not desired in most situations, this PR proposes to add a "expand" button that only appears once you've reached the height limit of the composer.

Also we have some planned GSoC Projects that would heavily benefit from this feature

#35975

and

#36526

Issue(s)

COMM-30

Steps to test or reproduce

  1. Enable the feature preview for the message expand feature
  2. Write something in the composer, until you reach the max height
  3. Click the expand button

Further comments

Summary by CodeRabbit

  • New Features

    • Added an optional expandable message composer input with expand/collapse controls and auto-collapse when cleared.
    • Added a Feature Preview toggle ("Expandable message composer") to opt in to the new input.
  • Documentation

    • Added localized title and description strings for the feature in English.
  • Tests

    • Added tests validating expand/collapse behavior and auto-collapse scenarios.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Sep 11, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is targeting the wrong base branch. It should target 7.12.0, but it targets 7.11.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Sep 11, 2025

⚠️ No Changeset found

Latest commit: 32d99c0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

Walkthrough

Adds an expandable message composer behind a feature-preview flag, introducing MessageComposerInputExpandable, wiring the feature flag into the client registry and i18n, updating exports, stories, and tests, and conditionally rendering the expandable input in the room message box.

Changes

Cohort / File(s) Summary
Room message box integration
apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx
Wraps the composer input in FeaturePreview/FeaturePreviewOn/FeaturePreviewOff and conditionally renders MessageComposerInputExpandable when expandableMessageComposer is enabled; otherwise renders the existing MessageComposerInput.
Feature preview registry
packages/ui-client/src/hooks/useFeaturePreviewList.ts
Adds 'expandableMessageComposer' to the FeaturesAvailable union and to the default feature preview list with i18n keys, description, image, group, and defaults.
Expandable composer component & exports
packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.tsx, packages/ui-composer/src/MessageComposer/index.ts, packages/ui-composer/src/MessageComposer/MessageComposer.stories.tsx
New MessageComposerInputExpandable forwardRef component (exported) with expand/collapse button, toggles height/maxHeight when expanded, auto-collapses on empty input; exported from MessageComposer index and demonstrated in story.
Tests
packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.spec.tsx
New tests covering expand-button visibility, expand/collapse toggling, inline-style changes, and auto-collapse when the input is cleared.
i18n
packages/i18n/src/locales/en.i18n.json
Adds Expandable_message_composer and Expandable_message_composer_description translation keys and strings.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant MB as MessageBox
  participant FP as FeaturePreview
  participant MCX as MessageComposerInputExpandable
  participant MCI as MessageComposerInput

  U->>MB: Open room composer
  MB->>FP: Is "expandableMessageComposer" enabled?
  alt enabled
    FP-->>MB: enabled
    MB->>MCX: Render expandable input (props forwarded)
  else disabled
    FP-->>MB: disabled
    MB->>MCI: Render standard input (props forwarded)
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant MCX as MessageComposerInputExpandable
  participant TA as Textarea

  U->>MCX: Type text
  MCX->>TA: Update value
  alt dimensions.blockSize > 100
    MCX-->>U: Show "Expand" button (i18n)
    U->>MCX: Click Expand
    MCX->>TA: Apply height=500, maxHeight=50vh
    MCX-->>U: Button becomes "Collapse"
    U->>MCX: Click Collapse or clear text
    MCX->>TA: Remove expanded styles (collapse)
    MCX-->>U: Button becomes "Expand"
  else
    MCX-->>U: No expand button shown
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • yash-rajpal

Poem

I nibble keys and hop with glee,
I stretched the box for words to be.
Click expand, then tap to fold,
A carrot-sized UX, brave and bold.
(_/) 🌿📝

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: Expandable Message Composer” succinctly and accurately describes the primary enhancement of adding an expandable message composer feature to the application, reflecting the main change in the pull request.
Linked Issues Check ✅ Passed The pull request implements the core objectives of COMM-30 by introducing an expandable composer component that remains compact by default, exposes an expand button at the configured height threshold, enlarges the input area on demand, and collapses appropriately, with supporting translations, feature flag integration, and tests verifying this behavior.
Out of Scope Changes Check ✅ Passed All modifications in this pull request are directly related to implementing the expandable message composer feature, including the new component, tests, translations, and feature flag registration, with no unrelated or extraneous changes detected.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/expand-composer

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 183953b and 47224cd.

⛔ Files ignored due to path filters (1)
  • packages/ui-composer/src/MessageComposer/__snapshots__/MessageComposer.spec.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • packages/ui-composer/src/MessageComposer/MessageComposer.stories.tsx (2 hunks)
  • packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.spec.tsx (1 hunks)
  • packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.tsx (1 hunks)
  • packages/ui-composer/src/MessageComposer/index.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.spec.tsx
  • packages/ui-composer/src/MessageComposer/MessageComposerInputExpandable.tsx
  • packages/ui-composer/src/MessageComposer/MessageComposer.stories.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Builds matrix rust bindings against alpine
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
packages/ui-composer/src/MessageComposer/index.ts (2)

8-8: LGTM!

The import follows the established pattern in this barrel file, correctly importing the new MessageComposerInputExpandable component from its module.


19-19: LGTM — export and integration verified.
Component is already imported and used in unit tests, Storybook stories, and the Meteor app.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Sep 11, 2025

Codecov Report

❌ Patch coverage is 94.87179% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.42%. Comparing base (e488086) to head (32d99c0).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #36920      +/-   ##
===========================================
+ Coverage    64.80%   67.42%   +2.61%     
===========================================
  Files         2988     3287     +299     
  Lines       106831   111781    +4950     
  Branches     19290    20560    +1270     
===========================================
+ Hits         69233    75367    +6134     
+ Misses       35372    33736    -1636     
- Partials      2226     2678     +452     
Flag Coverage Δ
e2e 57.37% <100.00%> (+11.05%) ⬆️
unit 71.40% <94.28%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member Author

@MartinSchoeler MartinSchoeler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following are missing:

  • A margin or padding to make the button not be on top of the text
  • When sending a message, the composer should collapse

@MartinSchoeler MartinSchoeler marked this pull request as ready for review September 24, 2025 17:21
@MartinSchoeler MartinSchoeler requested a review from a team as a code owner September 24, 2025 17:21
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (16)
packages/ui-composer/src/MessageComposerInputExpandable/index.ts (1)

1-1: Also re-export the props type from the folder index.

Consumers won’t be able to import the component’s props type from the package root because this index only re-exports the default. Re-export the type to keep the public API complete.

Apply:

 export { default as MessageComposerInputExpandable } from './MessageComposerInputExpandable';
+export type { ExpandComposerButtonProps } from './MessageComposerInputExpandable';
packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.tsx (2)

19-24: Auto-collapse should also react to external clears (controlled value).

Collapsing only on input events misses cases where the parent clears the value (e.g., after send). Watch props.value to collapse when it becomes empty.

Apply:

 	const { t } = useTranslation();
 
+	// Collapse when parent clears the input (controlled component)
+	useEffect(() => {
+		if (props.value === '' || props.value == null) {
+			setExpanded(false);
+		}
+	}, [props.value]);
+
 	const onChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
-		props.onChange?.(event);
-		if (event.target.value.length === 0) {
+		props.onChange?.(event);
+		if (event.currentTarget.value.length === 0) {
 			setExpanded(false);
 		}
 	};

49-51: Minor: merge conditional props for less churn.

Combine the conditional spread into one to reduce prop object allocations (non-blocking).

Apply:

-				{...(!!expanded && { height: 500 })}
-				{...(!!expanded && { maxHeight: '50vh' })}
+				{...(expanded ? { height: 500, maxHeight: '50vh' } : {})}
packages/i18n/src/locales/en.i18n.json (1)

2027-2028: Align casing with existing feature names and clarify behavior in description

Most feature names nearby use sentence case (e.g., “Adjustable font size”). Also, clarifying the “max height” trigger aligns with the PR objective.

Apply this diff:

-  "Expandable_message_composer": "Expandable Message Composer",
-  "Expandable_message_composer_description": "Adds a button to expand the message composer vertically",
+  "Expandable_message_composer": "Expandable message composer",
+  "Expandable_message_composer_description": "Shows an expand button when the composer reaches its maximum height",
packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.spec.tsx (8)

10-13: Avoid broad story snapshots; keep a lightweight smoke test instead

Snapshotting full DOM for every story increases flakiness and review noise.

Replace with a minimal render assertion:

-test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
-  const tree = render(<Story />);
-  expect(tree.baseElement).toMatchSnapshot();
-});
+test.each(testCases)(`renders %s`, (_storyname, Story) => {
+  const { container } = render(<Story />);
+  expect(container.firstChild).toBeTruthy();
+});

23-37: Make button queries resilient and i18n-safe

Relying on getByRole('button') and asserting aria-label === 'Expand' is brittle with additional buttons and localization.

Use accessible name filtering and assertions:

-const expandButton = screen.getByRole('button');
+const expandButton = screen.getByRole('button', { name: /expand/i });
 expect(expandButton).toBeInTheDocument();
-expect(expandButton).toHaveAttribute('aria-label', 'Expand');
+expect(expandButton).toHaveAccessibleName(/expand/i);

If toHaveAccessibleName isn’t available, keep the name filter and drop the attribute assertion.


39-52: Tighten the negative query to the intended control

Future UI changes could add other buttons; scope the query by accessible name.

-const expandButton = screen.queryByRole('button');
+const expandButton = screen.queryByRole('button', { name: /expand/i });
expect(expandButton).not.toBeInTheDocument();

65-78: Avoid hard-coding expanded height; expose/read a constant or prop

Asserting '500px' couples tests to implementation details.

  • Export a constant (e.g., DEFAULT_EXPANDED_HEIGHT = 500) from the component and import it in tests, or
  • Allow an expandedHeight prop for testability and pass it explicitly in tests.

Example:

-<MessageComposerInputExpandable
+<MessageComposerInputExpandable
+  expandedHeight={500}
   dimensions={{ inlineSize: 400, blockSize: 120 }}
   placeholder='Type a message...'
/>

Then assert using that value.


71-76: Use userEvent and await DOM updates to reduce flakiness

State updates may be async; fireEvent with immediate assertions can be flaky.

  • Switch to @testing-library/user-event.
  • Await changes with waitFor.

Minimal example:

-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';

- fireEvent.click(expandButton);
+ await userEvent.click(expandButton);
- expect(textarea).toHaveStyle({ height: '500px' });
+ await waitFor(() => expect(textarea).toHaveStyle({ height: '500px' }));

Apply similarly for collapse and auto‑collapse.

Also applies to: 98-104, 132-135


103-105: Also assert the accessible name after collapse

Strengthen the assertion by checking the control’s name toggles back.

 expect(textarea).not.toHaveStyle({ maxHeight: '500px' });
-expect(expandButton).toHaveAttribute('aria-label', 'Expand');
+expect(expandButton).toHaveAccessibleName(/expand/i);

23-37: Test keyboard activation for accessibility

Add tests for Space/Enter to toggle expand/collapse on the button.

Example:

await userEvent.keyboard('{Tab}'); // focus button
await userEvent.keyboard('{Enter}');
expect(textarea).toHaveStyle({ height: `${DEFAULT_EXPANDED_HEIGHT}px` });

Also applies to: 54-63, 80-89, 107-116


23-37: De-duplicate the “100px threshold” assumption

Both tests rely on a 100px threshold that’s likely hard-coded in the component. Prefer importing a shared constant or setting via prop.

  • Export THRESHOLD_BLOCK_SIZE from the component and import it here; or
  • Accept a minBlockSizeToShowExpand prop in tests and stories.

Also applies to: 39-52

packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.stories.tsx (3)

87-101: Avoid controlled value without onChange in Storybook

Providing value without onChange produces a read‑only control and dev warnings. Use defaultValue for static content.

-      value='This is some sample text to demonstrate the expandable input with content.'
+      defaultValue='This is some sample text to demonstrate the expandable input with content.'

Alternatively, add readOnly if you must keep value.


103-116: Don’t duplicate the 100px threshold in stories

Keep the threshold in one place to prevent drift.

  • Import a THRESHOLD_BLOCK_SIZE constant from the component and reference it in the comment/args; or
  • Expose a prop (e.g., minBlockSizeToShowExpand) for demos/tests and pass 100 explicitly.

17-21: Prefer CSF3 StoryObj for simpler stories

Using StoryObj reduces boilerplate and plays nicer with tooling and test reuse.

Refactor to:

import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta<typeof MessageComposerInputExpandable> = { title: 'Components/MessageComposerInputExpandable', component: MessageComposerInputExpandable };
export default meta;
export type Story = StoryObj<typeof MessageComposerInputExpandable>;

Then define stories with args instead of inline functions where possible.

apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx (1)

427-455: Extract shared input props and merge styles correctly

  • Factor out repeated props into a commonInputProps object and spread it into both <MessageComposerInputExpandable> and <MessageComposerInput>.
  • In packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.tsx (around lines 45–51), merge the incoming style prop after the component’s computed height/maxHeight so parent overrides take effect.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b2905b and 63f202f.

⛔ Files ignored due to path filters (2)
  • apps/meteor/public/images/featurePreview/expandable-composer.png is excluded by !**/*.png
  • packages/ui-composer/src/MessageComposerInputExpandable/__snapshots__/MessageComposerInputExpandable.spec.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx (3 hunks)
  • packages/i18n/src/locales/en.i18n.json (1 hunks)
  • packages/ui-client/src/hooks/useFeaturePreviewList.ts (2 hunks)
  • packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.spec.tsx (1 hunks)
  • packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.stories.tsx (1 hunks)
  • packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.tsx (1 hunks)
  • packages/ui-composer/src/MessageComposerInputExpandable/index.ts (1 hunks)
  • packages/ui-composer/src/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-16T22:08:51.490Z
Learnt from: CR
PR: RocketChat/Rocket.Chat#0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-09-16T22:08:51.490Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use descriptive test names that clearly communicate expected behavior

Applied to files:

  • packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.spec.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (8)
packages/ui-client/src/hooks/useFeaturePreviewList.ts (2)

8-9: Union type updates look good.

Adding 'secondarySidebar' and 'expandableMessageComposer' to the union aligns the type with defaults below.


77-85: Verified i18n keys and image asset exist. Both Expandable_message_composer and Expandable_message_composer_description are present in packages/i18n/src/locales/en.i18n.json and images/featurePreview/expandable-composer.png exists.

packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.tsx (2)

15-16: forwardRef generic stays correct after props fix.

No change needed here beyond the props type update above.


38-42: Verify Fuselage icon names in MessageComposerInputExpandable.tsx
Translations ‘Collapse’/‘Expand’ are present; confirm ‘arrow-collapse’/‘arrow-expand’ are valid Fuselage icons (e.g., swap to ‘chevron-up’/‘chevron-down’ if not).

packages/ui-composer/src/index.ts (1)

3-3: Barrel re-export is fine; ensure props type is exposed upstream.

With export * from './MessageComposerInputExpandable', re-exporting the props type from the folder index (see comment in that file) will make it available from the package root.

apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx (1)

4-4: Exports confirmed: FeaturePreview, FeaturePreviewOn, FeaturePreviewOff, and useSafeRefCallback are exported from @rocket.chat/ui-client; MessageComposerInputExpandable is exported from @rocket.chat/ui-composer.

packages/ui-composer/src/MessageComposerInputExpandable/MessageComposerInputExpandable.spec.tsx (2)

15-20: Matcher already registered in jest setup
The jest-axe matcher is globally extended in jest-presets/src/client/jest-setup.ts (import { toHaveNoViolations } from 'jest-axe'; expect.extend(toHaveNoViolations);), so no additional import is needed.

Likely an incorrect or invalid review comment.


1-3: Verify composeStories import for Storybook v8
Storybook 8 exports composeStories from @storybook/test (renderer-agnostic). If you’ve upgraded to v8, replace:

-import { composeStories } from '@storybook/react';
+import { composeStories } from '@storybook/test';

Ensure @storybook/test is installed and adjust imports accordingly.

@MartinSchoeler MartinSchoeler added this to the 7.12.0 milestone Sep 24, 2025
@dougfabris dougfabris force-pushed the feat/expand-composer branch from 3db6473 to 3211839 Compare October 7, 2025 23:07
@dougfabris dougfabris force-pushed the feat/expand-composer branch from 3211839 to 47224cd Compare October 7, 2025 23:08
@scuciatto scuciatto added the stat: QA assured Means it has been tested and approved by a company insider label Oct 14, 2025
@dionisio-bot dionisio-bot bot added the stat: ready to merge PR tested and approved waiting for merge label Oct 14, 2025
@kodiakhq kodiakhq bot merged commit 6d605e6 into develop Oct 14, 2025
49 checks passed
@kodiakhq kodiakhq bot deleted the feat/expand-composer branch October 14, 2025 18:40
antm-rp pushed a commit to antm-rp/Rocket.Chat that referenced this pull request Oct 16, 2025
Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants