From 41aadba9217563351a2e5c7740a688ecac03b0bb Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 16 Apr 2025 17:20:21 -0500 Subject: [PATCH 01/12] add minimized state to SearchField --- .../@react-spectrum/s2/src/SearchField.tsx | 45 ++++++++++++++++++- .../s2/stories/SearchField.stories.tsx | 8 ++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index 04a9f7f811a..fbdb7f5687d 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -10,6 +10,7 @@ * governing permissions and limitations under the License. */ +import {ActionButton} from './ActionButton'; import { SearchField as AriaSearchField, SearchFieldProps as AriaSearchFieldProps, @@ -18,7 +19,7 @@ import { } from 'react-aria-components'; import {centerBaseline} from './CenterBaseline'; import {ClearButton} from './ClearButton'; -import {createContext, forwardRef, Ref, useContext, useImperativeHandle, useRef} from 'react'; +import {createContext, forwardRef, Ref, useContext, useEffect, useImperativeHandle, useRef} from 'react'; import {createFocusableRef} from '@react-spectrum/utils'; import {field, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'}; import {FieldGroup, FieldLabel, HelpText, Input} from './Field'; @@ -29,6 +30,7 @@ import {IconContext} from './Icon'; import {raw} from '../style/style-macro' with {type: 'macro'}; import SearchIcon from '../s2wf-icons/S2_Icon_Search_20_N.svg'; import {TextFieldRef} from '@react-types/textfield'; +import {useControlledState} from '@react-stately/utils'; import {useSpectrumContextProps} from './useSpectrumContextProps'; export interface SearchFieldProps extends Omit, StyleProps, SpectrumLabelableProps, HelpTextProps { @@ -37,7 +39,17 @@ export interface SearchFieldProps extends Omit void } export const SearchFieldContext = createContext, TextFieldRef>>(null); @@ -58,12 +70,25 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: labelAlign = 'start', UNSAFE_className = '', UNSAFE_style, + styles, + isMinimized: isMinimizedProp, + defaultMinimized = false, + onMinimizeChange, ...searchFieldProps } = props; + let [isMinimized, setIsMinimized] = useControlledState(isMinimizedProp, defaultMinimized, onMinimizeChange); + let domRef = useRef(null); let inputRef = useRef(null); + // Focus the input when the field is expanded. + useEffect(() => { + if (!isMinimized && inputRef.current) { + inputRef.current.focus(); + } + }, [isMinimized]); + // Expose imperative interface for ref useImperativeHandle(ref, () => ({ ...createFocusableRef(domRef, inputRef), @@ -77,6 +102,22 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: } })); + if (isMinimized) { + return ( + setIsMinimized(false)} + UNSAFE_className={UNSAFE_className} + UNSAFE_style={UNSAFE_style} + styles={styles}> + + + ); + } + return ( ( ContextualHelpExample.args = { label: 'Search' }; + +export const Minimized = (args: any) => ; + +Minimized.args = { + label: null, + ariaLabel: 'Search', + defaultMinimized: true +}; From 8e440102dee686b1da81944f03497572e12f2cbb Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Fri, 18 Apr 2025 15:51:38 -0500 Subject: [PATCH 02/12] put button inside FieldGroup --- .../@react-spectrum/s2/src/SearchField.tsx | 117 ++++++++++-------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index fbdb7f5687d..8f287f62ca8 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -141,58 +141,75 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: labelPosition, isInForm: !!formContext }, props.styles)}> - {({isDisabled, isInvalid, isEmpty}) => (<> - {label && - {label} - } - - ( + <> + {label && !isMinimized && ( + + {label} + + )} + + - - - - {!isEmpty && !searchFieldProps.isReadOnly && } - - - {errorMessage} - - )} + }] + ]}> + {isMinimized ? ( + setIsMinimized(false)}> + + + ) : ( + + )} + + {!isMinimized && ( + + )} + {!isEmpty && !isMinimized && !searchFieldProps.isReadOnly && } + + + {errorMessage} + + + )} ); }); From 5e3aa176cf9d4064853c366be8a48bda8a211f17 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Fri, 18 Apr 2025 15:52:00 -0500 Subject: [PATCH 03/12] add container with width to story --- .../s2/stories/SearchField.stories.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/@react-spectrum/s2/stories/SearchField.stories.tsx b/packages/@react-spectrum/s2/stories/SearchField.stories.tsx index 0b18614c8b4..9108a919323 100644 --- a/packages/@react-spectrum/s2/stories/SearchField.stories.tsx +++ b/packages/@react-spectrum/s2/stories/SearchField.stories.tsx @@ -79,10 +79,18 @@ ContextualHelpExample.args = { label: 'Search' }; -export const Minimized = (args: any) => ; - -Minimized.args = { - label: null, - ariaLabel: 'Search', - defaultMinimized: true +export const Minimized = { + render: (args: any) => , + args: { + label: null, + ariaLabel: 'Search', + defaultMinimized: true + }, + decorators: [ + (Story) => ( +
+ +
+ ) + ] }; From afce5da9e66c567ac26c9a242ac2996f022ebb38 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Fri, 18 Apr 2025 16:42:38 -0500 Subject: [PATCH 04/12] collapse on blur --- packages/@react-spectrum/s2/src/SearchField.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index 8f287f62ca8..fe017a01cd5 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -27,10 +27,12 @@ import {fontRelative, style} from '../style' with {type: 'macro'}; import {FormContext, useFormProps} from './Form'; import {HelpTextProps, SpectrumLabelableProps} from '@react-types/shared'; import {IconContext} from './Icon'; +import {mergeProps} from 'react-aria'; import {raw} from '../style/style-macro' with {type: 'macro'}; import SearchIcon from '../s2wf-icons/S2_Icon_Search_20_N.svg'; import {TextFieldRef} from '@react-types/textfield'; import {useControlledState} from '@react-stately/utils'; +import {useFocus} from '@react-aria/interactions'; import {useSpectrumContextProps} from './useSpectrumContextProps'; export interface SearchFieldProps extends Omit, StyleProps, SpectrumLabelableProps, HelpTextProps { @@ -89,6 +91,15 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: } }, [isMinimized]); + let {focusProps} = useFocus({ + onBlur: () => { + // Minimize the field when it loses focus and is empty (if minimization is enabled) + if ((isMinimizedProp !== undefined || defaultMinimized) && inputRef.current?.value === '') { + setIsMinimized(true); + } + } + }); + // Expose imperative interface for ref useImperativeHandle(ref, () => ({ ...createFocusableRef(domRef, inputRef), @@ -120,7 +131,7 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: return ( Date: Mon, 21 Apr 2025 10:02:42 -0500 Subject: [PATCH 05/12] match margin --- .../@react-spectrum/s2/src/SearchField.tsx | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index fe017a01cd5..841b9c7f239 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -116,14 +116,12 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: if (isMinimized) { return ( setIsMinimized(false)} - UNSAFE_className={UNSAFE_className} - UNSAFE_style={UNSAFE_style} - styles={styles}> + onPress={() => setIsMinimized(false)}> ); @@ -154,7 +152,7 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: }, props.styles)}> {({isDisabled, isInvalid, isEmpty}) => ( <> - {label && !isMinimized && ( + {label && ( - {isMinimized ? ( - setIsMinimized(false)}> - - - ) : ( - - )} + - {!isMinimized && ( - - )} - {!isEmpty && !isMinimized && !searchFieldProps.isReadOnly && } + + {!isEmpty && !searchFieldProps.isReadOnly && } Date: Mon, 21 Apr 2025 10:28:15 -0500 Subject: [PATCH 06/12] revert formatting --- .../@react-spectrum/s2/src/SearchField.tsx | 104 +++++++++--------- 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index 841b9c7f239..6bfcdf7e4b5 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -150,62 +150,58 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: labelPosition, isInForm: !!formContext }, props.styles)}> - {({isDisabled, isInvalid, isEmpty}) => ( - <> - {label && ( - - {label} - - )} - - (<> + {label && + {label} + } + + - - - - {!isEmpty && !searchFieldProps.isReadOnly && } - - - {errorMessage} - - - )} + }), + styles: style({ + size: fontRelative(20), + marginStart: '--iconMargin' + }) + }] + ]}> + + + + {!isEmpty && !searchFieldProps.isReadOnly && } + + + {errorMessage} + + )} ); }); From 203bc5b57193a40a7883c77d61e9bf01c5af21ec Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Mon, 21 Apr 2025 10:37:28 -0500 Subject: [PATCH 07/12] lint --- packages/@react-spectrum/s2/src/SearchField.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index 6bfcdf7e4b5..df79b055074 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -72,7 +72,6 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: labelAlign = 'start', UNSAFE_className = '', UNSAFE_style, - styles, isMinimized: isMinimizedProp, defaultMinimized = false, onMinimizeChange, From 63a635905ff79f485ebe2a1e284382831c92aba2 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 23 Apr 2025 11:07:15 -0500 Subject: [PATCH 08/12] improve animation --- packages/@react-aria/utils/src/animation.ts | 7 +- .../@react-spectrum/s2/src/SearchField.tsx | 264 +++++++++++------- 2 files changed, 176 insertions(+), 95 deletions(-) diff --git a/packages/@react-aria/utils/src/animation.ts b/packages/@react-aria/utils/src/animation.ts index c613b451163..54d6a649aa6 100644 --- a/packages/@react-aria/utils/src/animation.ts +++ b/packages/@react-aria/utils/src/animation.ts @@ -15,7 +15,12 @@ import {RefObject, useCallback, useState} from 'react'; import {useLayoutEffect} from './useLayoutEffect'; export function useEnterAnimation(ref: RefObject, isReady: boolean = true): boolean { - let [isEntering, setEntering] = useState(true); + let [isEntering, setEntering] = useState(isReady); + useLayoutEffect(() => { + if (isReady) { + setEntering(true); + } + }, [isReady]); let isAnimationReady = isEntering && isReady; // There are two cases for entry animations: diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index df79b055074..4a449d09770 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -32,6 +32,7 @@ import {raw} from '../style/style-macro' with {type: 'macro'}; import SearchIcon from '../s2wf-icons/S2_Icon_Search_20_N.svg'; import {TextFieldRef} from '@react-types/textfield'; import {useControlledState} from '@react-stately/utils'; +import {useEnterAnimation, useExitAnimation} from '@react-aria/utils'; import {useFocus} from '@react-aria/interactions'; import {useSpectrumContextProps} from './useSpectrumContextProps'; @@ -80,20 +81,29 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: let [isMinimized, setIsMinimized] = useControlledState(isMinimizedProp, defaultMinimized, onMinimizeChange); - let domRef = useRef(null); + let searchFieldRef = useRef(null); + let actionButtonRef = useRef(null); + let containerRef = useRef(null); + + let showSearchField = !isMinimized; + let showActionButton = isMinimized; + let isSearchFieldExiting = useExitAnimation(searchFieldRef, showSearchField); + let isActionButtonExiting = useExitAnimation(actionButtonRef, showActionButton); + let isSearchFieldEntering = useEnterAnimation(searchFieldRef, showSearchField); + let isActionButtonEntering = useEnterAnimation(actionButtonRef, showActionButton); + let inputRef = useRef(null); - // Focus the input when the field is expanded. useEffect(() => { - if (!isMinimized && inputRef.current) { + if (showSearchField && !isSearchFieldEntering && !isSearchFieldExiting && inputRef.current) { inputRef.current.focus(); } - }, [isMinimized]); + }, [showSearchField, isSearchFieldEntering, isSearchFieldExiting]); let {focusProps} = useFocus({ - onBlur: () => { + onBlur: (e) => { // Minimize the field when it loses focus and is empty (if minimization is enabled) - if ((isMinimizedProp !== undefined || defaultMinimized) && inputRef.current?.value === '') { + if ((isMinimizedProp !== undefined || defaultMinimized) && inputRef.current?.value === '' && !containerRef.current?.contains(e.relatedTarget as Element)) { setIsMinimized(true); } } @@ -101,7 +111,7 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: // Expose imperative interface for ref useImperativeHandle(ref, () => ({ - ...createFocusableRef(domRef, inputRef), + ...createFocusableRef(searchFieldRef, inputRef), select() { if (inputRef.current) { inputRef.current.select(); @@ -112,95 +122,161 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: } })); - if (isMinimized) { - return ( - setIsMinimized(false)}> - - - ); - } + const searchFieldStyles = style({ + ...field(), + '--iconMargin': { + type: 'marginTop', + value: fontRelative(-2) + }, + color: { + default: 'neutral', + isDisabled: { + default: 'disabled', + forcedColors: 'GrayText' + } + }, + width: { + default: 200, + isEntering: 0, + isExiting: 0 + }, + minWidth: { + default: 200, + isEntering: 0, + isExiting: 0 + }, + opacity: { + default: 1, + isEntering: 0, + isExiting: 0 + }, + overflow: 'hidden', + transition: '[width, opacity, min-width]', + transitionDuration: 200, + transitionTimingFunction: 'in-out', + verticalAlign: 'middle' + }, getAllowedOverrides()); + + const actionButtonStyles = style({ + position: { + isEntering: 'absolute', + isExitingSearchField: 'absolute', + default: 'relative' + }, + top: { + isEntering: 0, + isExitingSearchField: 0 + }, + left: { + isEntering: 0, + isExitingSearchField: 0 + }, + marginStart: { + default: 12, + isEntering: 0, + isExitingSearchField: 0 + }, + opacity: { + default: 1, + isEntering: 0, + isExiting: 0 + }, + transition: '[opacity, scale]', + transitionDuration: 200, + transitionTimingFunction: 'in-out', + verticalAlign: 'middle' + }, getAllowedOverrides()); + + const isSearchFieldExitingForButton = !showSearchField && isSearchFieldExiting; return ( - - {({isDisabled, isInvalid, isEmpty}) => (<> - {label && - {label} - } - + {(showActionButton || isActionButtonExiting) && ( + } + styles={actionButtonStyles({ + isEntering: isActionButtonEntering, + isExiting: isActionButtonExiting, + isExitingSearchField: isSearchFieldExitingForButton + }) as any} + aria-label={typeof label === 'string' ? label : props['aria-label'] || 'Search'} size={props.size} - styles={style({ - borderRadius: 'full', - paddingStart: 'pill', - paddingEnd: 0 - })}> - - - - - {!isEmpty && !searchFieldProps.isReadOnly && } - - - {errorMessage} - - )} - + isQuiet + isDisabled={props.isDisabled} + onPress={() => setIsMinimized(false)}> + + + )} + + {(showSearchField || isSearchFieldExiting) && ( + + {({isDisabled: racIsDisabled, isInvalid, isEmpty}) => (<> + {label && + {label} + } + + + + + + {!isEmpty && !searchFieldProps.isReadOnly && } + + + {errorMessage} + + )} + + )} + ); }); From 968f8cf0711c862006d44cec1b080b0bbe27ab16 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 23 Apr 2025 17:17:06 -0500 Subject: [PATCH 09/12] version with no transition --- packages/@react-aria/utils/src/animation.ts | 7 +- .../@react-spectrum/s2/src/SearchField.tsx | 264 +++++++----------- 2 files changed, 95 insertions(+), 176 deletions(-) diff --git a/packages/@react-aria/utils/src/animation.ts b/packages/@react-aria/utils/src/animation.ts index 54d6a649aa6..c613b451163 100644 --- a/packages/@react-aria/utils/src/animation.ts +++ b/packages/@react-aria/utils/src/animation.ts @@ -15,12 +15,7 @@ import {RefObject, useCallback, useState} from 'react'; import {useLayoutEffect} from './useLayoutEffect'; export function useEnterAnimation(ref: RefObject, isReady: boolean = true): boolean { - let [isEntering, setEntering] = useState(isReady); - useLayoutEffect(() => { - if (isReady) { - setEntering(true); - } - }, [isReady]); + let [isEntering, setEntering] = useState(true); let isAnimationReady = isEntering && isReady; // There are two cases for entry animations: diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index 4a449d09770..df79b055074 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -32,7 +32,6 @@ import {raw} from '../style/style-macro' with {type: 'macro'}; import SearchIcon from '../s2wf-icons/S2_Icon_Search_20_N.svg'; import {TextFieldRef} from '@react-types/textfield'; import {useControlledState} from '@react-stately/utils'; -import {useEnterAnimation, useExitAnimation} from '@react-aria/utils'; import {useFocus} from '@react-aria/interactions'; import {useSpectrumContextProps} from './useSpectrumContextProps'; @@ -81,29 +80,20 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: let [isMinimized, setIsMinimized] = useControlledState(isMinimizedProp, defaultMinimized, onMinimizeChange); - let searchFieldRef = useRef(null); - let actionButtonRef = useRef(null); - let containerRef = useRef(null); - - let showSearchField = !isMinimized; - let showActionButton = isMinimized; - let isSearchFieldExiting = useExitAnimation(searchFieldRef, showSearchField); - let isActionButtonExiting = useExitAnimation(actionButtonRef, showActionButton); - let isSearchFieldEntering = useEnterAnimation(searchFieldRef, showSearchField); - let isActionButtonEntering = useEnterAnimation(actionButtonRef, showActionButton); - + let domRef = useRef(null); let inputRef = useRef(null); + // Focus the input when the field is expanded. useEffect(() => { - if (showSearchField && !isSearchFieldEntering && !isSearchFieldExiting && inputRef.current) { + if (!isMinimized && inputRef.current) { inputRef.current.focus(); } - }, [showSearchField, isSearchFieldEntering, isSearchFieldExiting]); + }, [isMinimized]); let {focusProps} = useFocus({ - onBlur: (e) => { + onBlur: () => { // Minimize the field when it loses focus and is empty (if minimization is enabled) - if ((isMinimizedProp !== undefined || defaultMinimized) && inputRef.current?.value === '' && !containerRef.current?.contains(e.relatedTarget as Element)) { + if ((isMinimizedProp !== undefined || defaultMinimized) && inputRef.current?.value === '') { setIsMinimized(true); } } @@ -111,7 +101,7 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: // Expose imperative interface for ref useImperativeHandle(ref, () => ({ - ...createFocusableRef(searchFieldRef, inputRef), + ...createFocusableRef(domRef, inputRef), select() { if (inputRef.current) { inputRef.current.select(); @@ -122,161 +112,95 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: } })); - const searchFieldStyles = style({ - ...field(), - '--iconMargin': { - type: 'marginTop', - value: fontRelative(-2) - }, - color: { - default: 'neutral', - isDisabled: { - default: 'disabled', - forcedColors: 'GrayText' - } - }, - width: { - default: 200, - isEntering: 0, - isExiting: 0 - }, - minWidth: { - default: 200, - isEntering: 0, - isExiting: 0 - }, - opacity: { - default: 1, - isEntering: 0, - isExiting: 0 - }, - overflow: 'hidden', - transition: '[width, opacity, min-width]', - transitionDuration: 200, - transitionTimingFunction: 'in-out', - verticalAlign: 'middle' - }, getAllowedOverrides()); - - const actionButtonStyles = style({ - position: { - isEntering: 'absolute', - isExitingSearchField: 'absolute', - default: 'relative' - }, - top: { - isEntering: 0, - isExitingSearchField: 0 - }, - left: { - isEntering: 0, - isExitingSearchField: 0 - }, - marginStart: { - default: 12, - isEntering: 0, - isExitingSearchField: 0 - }, - opacity: { - default: 1, - isEntering: 0, - isExiting: 0 - }, - transition: '[opacity, scale]', - transitionDuration: 200, - transitionTimingFunction: 'in-out', - verticalAlign: 'middle' - }, getAllowedOverrides()); - - const isSearchFieldExitingForButton = !showSearchField && isSearchFieldExiting; + if (isMinimized) { + return ( + setIsMinimized(false)}> + + + ); + } return ( -
- {(showActionButton || isActionButtonExiting) && ( - } - styles={actionButtonStyles({ - isEntering: isActionButtonEntering, - isExiting: isActionButtonExiting, - isExitingSearchField: isSearchFieldExitingForButton - }) as any} - aria-label={typeof label === 'string' ? label : props['aria-label'] || 'Search'} + + {({isDisabled, isInvalid, isEmpty}) => (<> + {label && setIsMinimized(false)}> - - - )} - - {(showSearchField || isSearchFieldExiting) && ( - - {({isDisabled: racIsDisabled, isInvalid, isEmpty}) => (<> - {label && - {label} - } - - - - - - {!isEmpty && !searchFieldProps.isReadOnly && } - - - {errorMessage} - - )} - - )} -
+ labelPosition={labelPosition} + labelAlign={labelAlign} + necessityIndicator={necessityIndicator} + contextualHelp={props.contextualHelp}> + {label} + } + + + + + + {!isEmpty && !searchFieldProps.isReadOnly && } + + + {errorMessage} + + )} + ); }); From 3dc35562a8d76c4cbd2d6c90cdc5ec343520e0fd Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 23 Apr 2025 17:45:59 -0500 Subject: [PATCH 10/12] fix animation issues --- packages/@react-spectrum/s2/src/Field.tsx | 5 +- .../@react-spectrum/s2/src/SearchField.tsx | 182 +++++++++--------- .../s2/stories/SearchField.stories.tsx | 2 +- 3 files changed, 100 insertions(+), 89 deletions(-) diff --git a/packages/@react-spectrum/s2/src/Field.tsx b/packages/@react-spectrum/s2/src/Field.tsx index 5c1d45efe3d..a64b0600f65 100644 --- a/packages/@react-spectrum/s2/src/Field.tsx +++ b/packages/@react-spectrum/s2/src/Field.tsx @@ -193,10 +193,11 @@ const fieldGroupStyles = style({ }); export const FieldGroup = forwardRef(function FieldGroup(props: FieldGroupProps, ref: ForwardedRef) { + let {UNSAFE_style, ...rest} = props; return ( { // Forward focus to input element when clicking on a non-interactive child (e.g. icon or padding) if (e.pointerType === 'mouse' && !(e.target as Element).closest('button,input,textarea')) { @@ -210,7 +211,7 @@ export const FieldGroup = forwardRef(function FieldGroup(props: FieldGroupProps, e.currentTarget.querySelector('input')?.focus(); } }} - style={props.UNSAFE_style} + style={UNSAFE_style} className={renderProps => (props.UNSAFE_className || '') + ' ' + centerBaselineBefore + mergeStyles( fieldGroupStyles({...renderProps, size: props.size || 'M'}), props.styles diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index df79b055074..af9d279846a 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -112,95 +112,105 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: } })); - if (isMinimized) { - return ( - setIsMinimized(false)}> - - - ); - } - return ( - - {({isDisabled, isInvalid, isEmpty}) => (<> - {label && - {label} - } - + {isMinimized && ( + - setIsMinimized(false)}> + + + )} + + {({isDisabled, isInvalid, isEmpty}) => (<> + {label && + {label} + } + + - - - - {!isEmpty && !searchFieldProps.isReadOnly && } - - - {errorMessage} - - )} - + }] + ]}> + + + + {!isEmpty && !searchFieldProps.isReadOnly && } + + + {errorMessage} + + )} + + ); }); diff --git a/packages/@react-spectrum/s2/stories/SearchField.stories.tsx b/packages/@react-spectrum/s2/stories/SearchField.stories.tsx index 9108a919323..f3e364488f7 100644 --- a/packages/@react-spectrum/s2/stories/SearchField.stories.tsx +++ b/packages/@react-spectrum/s2/stories/SearchField.stories.tsx @@ -88,7 +88,7 @@ export const Minimized = { }, decorators: [ (Story) => ( -
+
) From 4dedb8a58b026b90a38431648beb234f538b0bd5 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 23 Apr 2025 17:47:56 -0500 Subject: [PATCH 11/12] allow customizing transition duration and timing function --- packages/@react-spectrum/s2/src/SearchField.tsx | 8 ++++++-- .../@react-spectrum/s2/stories/SearchField.stories.tsx | 10 +++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index af9d279846a..8e9c4a04a5c 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -51,7 +51,11 @@ export interface SearchFieldProps extends Omit void + onMinimizeChange?: (isMinimized: boolean) => void, + /** The duration of the transition animation (for testing only). */ + transitionDuration?: number, + /** The timing function of the transition animation (for testing only). */ + transitionTimingFunction?: string } export const SearchFieldContext = createContext, TextFieldRef>>(null); @@ -166,7 +170,7 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: isDisabled={isDisabled} size={props.size} UNSAFE_style={{ - transition: 'width 200ms ease, opacity 200ms ease', + transition: `width ${props.transitionDuration || 200}ms ${props.transitionTimingFunction || 'ease'}, opacity ${props.transitionDuration || 200}ms ${props.transitionTimingFunction || 'ease'}`, width: isMinimized ? 0 : '100%', opacity: isMinimized ? 0 : 1, visibility: isMinimized ? 'hidden' : 'visible' diff --git a/packages/@react-spectrum/s2/stories/SearchField.stories.tsx b/packages/@react-spectrum/s2/stories/SearchField.stories.tsx index f3e364488f7..1e7460cc05a 100644 --- a/packages/@react-spectrum/s2/stories/SearchField.stories.tsx +++ b/packages/@react-spectrum/s2/stories/SearchField.stories.tsx @@ -30,7 +30,15 @@ const meta: Meta = { }, tags: ['autodocs'], argTypes: { - ...categorizeArgTypes('Events', ['onChange', 'onClear', 'onSubmit']) + ...categorizeArgTypes('Events', ['onChange', 'onClear', 'onSubmit']), + transitionDuration: { + control: 'number', + defaultValue: 200 + }, + transitionTimingFunction: { + control: 'select', + options: ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear'] + } }, title: 'SearchField' }; From 6ff790de9268c29bc038c08490753e275fa9c14a Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Thu, 24 Apr 2025 12:04:44 -0500 Subject: [PATCH 12/12] transition the ActionButton out to make transition smoother --- .../@react-spectrum/s2/src/SearchField.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/@react-spectrum/s2/src/SearchField.tsx b/packages/@react-spectrum/s2/src/SearchField.tsx index 8e9c4a04a5c..5524a0b25bb 100644 --- a/packages/@react-spectrum/s2/src/SearchField.tsx +++ b/packages/@react-spectrum/s2/src/SearchField.tsx @@ -118,21 +118,21 @@ export const SearchField = /*#__PURE__*/ forwardRef(function SearchField(props: return ( <> - {isMinimized && ( - setIsMinimized(false)}> - - - )} + setIsMinimized(false)}> + +