From b4a4911c473ecab17f23e70ca104368a2b8b03fc Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 14 Dec 2025 09:28:02 +0000 Subject: [PATCH] Fixes SearchField in Backstage UI Signed-off-by: Charles de Dreuille --- .changeset/hot-colts-float.md | 5 ++++ .../SearchField/SearchField.module.css | 19 +++++++++---- .../SearchField/SearchField.stories.tsx | 7 +++-- .../components/SearchField/SearchField.tsx | 28 +++++++++++++++---- 4 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 .changeset/hot-colts-float.md diff --git a/.changeset/hot-colts-float.md b/.changeset/hot-colts-float.md new file mode 100644 index 0000000000..8bfa32256c --- /dev/null +++ b/.changeset/hot-colts-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers). diff --git a/packages/ui/src/components/SearchField/SearchField.module.css b/packages/ui/src/components/SearchField/SearchField.module.css index e99c212b29..db11825364 100644 --- a/packages/ui/src/components/SearchField/SearchField.module.css +++ b/packages/ui/src/components/SearchField/SearchField.module.css @@ -40,24 +40,27 @@ } &[data-startCollapsed='true'] { - transition: flex-basis 0.3s ease-in-out; + transition: + flex-basis 0.3s ease-in-out, + width 0.3s ease-in-out, + max-width 0.3s ease-in-out; padding: 0; flex: 0 1 auto; &[data-collapsed='true'] { - flex-basis: 200px; - } - - &[data-collapsed='false'] { cursor: pointer; &[data-size='medium'] { flex-basis: 2.5rem; + width: 2.5rem; + max-width: 2.5rem; height: 2.5rem; } &[data-size='small'] { flex-basis: 2rem; + width: 2rem; + max-width: 2rem; height: 2rem; } @@ -79,6 +82,12 @@ } } } + + &[data-collapsed='false'] { + flex-basis: 200px; + width: 200px; + max-width: 200px; + } } } diff --git a/packages/ui/src/components/SearchField/SearchField.stories.tsx b/packages/ui/src/components/SearchField/SearchField.stories.tsx index 81bc090835..5b6cf2df07 100644 --- a/packages/ui/src/components/SearchField/SearchField.stories.tsx +++ b/packages/ui/src/components/SearchField/SearchField.stories.tsx @@ -165,9 +165,12 @@ export const StartCollapsed: Story = { }, render: args => ( - + + + + + - ), }; diff --git a/packages/ui/src/components/SearchField/SearchField.tsx b/packages/ui/src/components/SearchField/SearchField.tsx index 775b7accc6..f9068ec8d6 100644 --- a/packages/ui/src/components/SearchField/SearchField.tsx +++ b/packages/ui/src/components/SearchField/SearchField.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { forwardRef, useEffect, useState } from 'react'; +import { forwardRef, useEffect, useState, useRef } from 'react'; import { Input, SearchField as AriaSearchField, @@ -39,9 +39,6 @@ export const SearchField = forwardRef( 'aria-labelledby': ariaLabelledBy, } = props; - const [isCollapsed, setIsCollapsed] = useState(false); - const [shouldCollapse, setShouldCollapse] = useState(true); - useEffect(() => { if (!label && !ariaLabel && !ariaLabelledBy) { console.warn( @@ -71,6 +68,10 @@ export const SearchField = forwardRef( ...rest } = cleanedProps; + const [isCollapsed, setIsCollapsed] = useState(startCollapsed); + const [shouldCollapse, setShouldCollapse] = useState(true); + const inputRef = useRef(null); + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. const secondaryLabelText = secondaryLabel || (isRequired ? 'Required' : null); @@ -79,13 +80,26 @@ export const SearchField = forwardRef( props.onFocusChange?.(isFocused); if (shouldCollapse) { if (isFocused) { - setIsCollapsed(true); - } else { + // When focusing, expand the field setIsCollapsed(false); + } else { + // When blurring, collapse the field + setIsCollapsed(true); } } }; + const handleContainerClick = () => { + // If the field is collapsed (small), expand it and focus the input + if (startCollapsed && isCollapsed) { + setIsCollapsed(false); + // Focus the input after state update + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + } + }; + const handleChange = (value: string) => { props.onChange?.(value); if (value.length > 0) { @@ -119,6 +133,7 @@ export const SearchField = forwardRef( styles[classNames.inputWrapper], )} data-size={dataAttributes['data-size']} + onClick={handleContainerClick} > {icon !== false && (
(
)}