From c5b478c254535e7f3f880fbcc2245380a16075c7 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Fri, 10 Jul 2026 18:48:25 +0530 Subject: [PATCH 1/5] always show pre-selected value --- .../AutoComplete/AutoComplete.spec.tsx | 31 ++++++++++++ .../AutoComplete/AutoComplete.stories.tsx | 42 +++++++++++++++- .../components/AutoComplete/AutoComplete.tsx | 21 ++++++-- .../__snapshots__/AutoComplete.spec.tsx.snap | 49 +++++++++++++++++++ 4 files changed, 139 insertions(+), 4 deletions(-) diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx index 8b331cb11c..6b6f1fc687 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx @@ -124,4 +124,35 @@ describe('[Autocomplete functionality]', () => { screen.queryByRole('button', { name: 'test1' }), ).not.toBeInTheDocument(); }); + + it('should show pre-selected value even if options is empty', async () => { + render( + {}} + options={[]} + onChange={() => {}} + />, + ); + + expect(screen.getByRole('button', { name: 'test1' })).toBeInTheDocument(); + }); + + it('should show pre-selected value even if options does not contain this value', async () => { + render( + {}} + options={[ + { value: 'test2', label: 'test2' }, + { value: 'test3', label: 'test3' }, + ]} + onChange={() => {}} + />, + ); + + expect(screen.getByRole('button', { name: 'test1' })).toBeInTheDocument(); + }); }); diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx index c4cb114c43..be10739ab5 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryFn } from '@storybook/react-webpack5'; import type { ReactNode } from 'react'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { exampleAvatar, DECORATOR_LABEL } from '../../../.storybook/helpers'; import { Avatar } from '../Avatar'; @@ -101,3 +101,43 @@ export const Disabled = Template.bind({}); Disabled.args = { disabled: true, }; + +const allOptions = [ + { value: 'alice', label: 'Alice Johnson' }, + { value: 'bob', label: 'Bob Smith' }, + { value: 'carol', label: 'Carol White' }, + { value: 'david', label: 'David Brown' }, + { value: 'eva', label: 'Eva Martinez' }, +]; + +const AsyncTemplate: StoryFn> = (args) => { + const [filter, setFilter] = useState(''); + const [value, setValue] = useState(args.value ?? 'bob'); + const [options, setOptions] = useState([]); + + useEffect(() => { + const timer = setTimeout(() => { + setOptions(allOptions); + }, 3000); + + return () => clearTimeout(timer); + }, []); + + return ( + + ); +}; + +export const AsyncLoading = AsyncTemplate.bind({}); +AsyncLoading.args = { + value: 'bob', + placeholder: 'Search users...', +}; diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx index 466d85f990..f22722f1bb 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx @@ -36,9 +36,19 @@ const getSelected = ( if (!value) { return []; } - return typeof value === 'string' - ? options.filter((option) => option.value === value) - : options?.filter((option) => value.includes(option.value)); + + const values = typeof value === 'string' ? [value] : value; + + return values.map((v) => { + const option = options.find((option) => option.value === v); + + return ( + option ?? { + value: v, + label: v as TLabel, + } + ); + }); }; const isSelectedValid = @@ -120,6 +130,11 @@ function AutoComplete({ }); }, [value]); + useEffect(() => { + // Sync selected items when options change + setSelected(getSelected(value, options) || []); + }, [options, value]); + const handleSelect = useStableCallback( ([newValue]: OptionType) => { if (selected.some((item) => item.value === newValue)) { diff --git a/packages/fuselage/src/components/AutoComplete/__snapshots__/AutoComplete.spec.tsx.snap b/packages/fuselage/src/components/AutoComplete/__snapshots__/AutoComplete.spec.tsx.snap index 8bcbb3eb6e..7608dbd92f 100644 --- a/packages/fuselage/src/components/AutoComplete/__snapshots__/AutoComplete.spec.tsx.snap +++ b/packages/fuselage/src/components/AutoComplete/__snapshots__/AutoComplete.spec.tsx.snap @@ -1,5 +1,54 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`[AutoComplete Rendering] renders AsyncLoading without crashing 1`] = ` + +
+
+
+ + +
+
+ +
+
+
+ +`; + exports[`[AutoComplete Rendering] renders CustomItem without crashing 1`] = `
From 8c6abb36cbf87f2b29b93521ad298755b2439888 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Fri, 10 Jul 2026 18:55:21 +0530 Subject: [PATCH 2/5] add changeset --- .changeset/chatty-fans-guess.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chatty-fans-guess.md diff --git a/.changeset/chatty-fans-guess.md b/.changeset/chatty-fans-guess.md new file mode 100644 index 0000000000..0d471e8ec7 --- /dev/null +++ b/.changeset/chatty-fans-guess.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/fuselage': patch +--- + +Introduces `AutoComplete` to render pre-selected values when they are missing from `options`. From 4f1905eea5462bcee5c973aa71fa7bb9ef7ed18f Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Fri, 10 Jul 2026 18:58:22 +0530 Subject: [PATCH 3/5] refactor --- .../src/components/AutoComplete/AutoComplete.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx index be10739ab5..f35a6152d5 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.stories.tsx @@ -112,7 +112,7 @@ const allOptions = [ const AsyncTemplate: StoryFn> = (args) => { const [filter, setFilter] = useState(''); - const [value, setValue] = useState(args.value ?? 'bob'); + const [value, setValue] = useState(args.value || []); const [options, setOptions] = useState([]); useEffect(() => { From 4f71d4e39216a542b7a8e51dfac6fef9dda21f27 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 14 Jul 2026 15:32:41 +0530 Subject: [PATCH 4/5] fix: preserve options change and sync the value changes --- .../components/AutoComplete/AutoComplete.tsx | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx index f22722f1bb..fa58b51614 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx @@ -51,18 +51,6 @@ const getSelected = ( }); }; -const isSelectedValid = - (value: string | string[] | undefined) => - (selected: AutoCompleteOption) => { - if (!value) { - return false; - } - - return typeof value === 'string' - ? selected.value === value - : value.includes(selected.value); - }; - export type AutoCompleteProps = Omit< AllHTMLAttributes, 'value' | 'onChange' | 'is' @@ -122,18 +110,19 @@ function AutoComplete({ ); useEffect(() => { - // Validates if selected items are still valid after value changes - setSelected((selected) => { - return !selected.every(isSelectedValid(value)) - ? selected.filter(isSelectedValid(value)) - : selected; - }); - }, [value]); + // Preserves the selected items after options change and don't contain selected items anymore + setSelected((currentSelected) => { + const mergedOptions = new Map( + currentSelected.map((option) => [option.value, option]), + ); - useEffect(() => { - // Sync selected items when options change - setSelected(getSelected(value, options) || []); - }, [options, value]); + options.forEach((option) => { + mergedOptions.set(option.value, option); + }); + + return getSelected(value, [...mergedOptions.values()]); + }); + }, [value, options]); const handleSelect = useStableCallback( ([newValue]: OptionType) => { From d47c15a6eafcd87f1a09af094fedd89b026138f0 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Wed, 15 Jul 2026 02:44:44 +0530 Subject: [PATCH 5/5] update visual snapshot --- ...nputs-autocomplete--async-loading-desktop.png | Bin 0 -> 7837 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/fuselage/test/snapshots/inputs-autocomplete--async-loading-desktop.png diff --git a/packages/fuselage/test/snapshots/inputs-autocomplete--async-loading-desktop.png b/packages/fuselage/test/snapshots/inputs-autocomplete--async-loading-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..01eae54491603ee256d93d2327e01458bcca88f2 GIT binary patch literal 7837 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 Cze-#H literal 0 HcmV?d00001