Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add menuWidth & expose align props in ComboBox and SearchAutocomplete #5446

Merged
merged 10 commits into from Nov 22, 2023
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/
import {AriaButtonProps} from '@react-types/button';
import {classNames, useFocusableRef, useIsMobileDevice, useResizeObserver, useUnwrapDOMRef} from '@react-spectrum/utils';
import {classNames, dimensionValue, useFocusableRef, useIsMobileDevice, useResizeObserver, useUnwrapDOMRef} from '@react-spectrum/utils';
import {ClearButton} from '@react-spectrum/button';
import {DOMRefValue, FocusableRef} from '@react-types/shared';
import {Field} from '@react-spectrum/label';
Expand Down Expand Up @@ -70,7 +70,9 @@ function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocomp
menuTrigger = 'input',
shouldFlip = true,
direction = 'bottom',
align = 'end',
isQuiet,
menuWidth: customMenuWidth,
loadingState,
onLoadMore,
onSubmit = () => {},
Expand Down Expand Up @@ -130,8 +132,9 @@ function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocomp

useLayoutEffect(onResize, [scale, onResize]);

let width = isQuiet ? undefined : menuWidth;
let style = {
width: isQuiet ? undefined : menuWidth,
width: customMenuWidth ? dimensionValue(customMenuWidth) : width,
minWidth: isQuiet ? `calc(${menuWidth}px + calc(2 * var(--spectrum-dropdown-quiet-offset)))` : menuWidth
};

Expand Down Expand Up @@ -161,7 +164,7 @@ function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocomp
UNSAFE_className={classNames(styles, 'spectrum-InputGroup-popover', {'spectrum-InputGroup-popover--quiet': isQuiet})}
ref={popoverRef}
triggerRef={inputRef}
placement={`${direction} end`}
placement={`${direction} ${align}`}
sookmax marked this conversation as resolved.
Show resolved Hide resolved
hideArrow
isNonModal
shouldFlip={shouldFlip}>
Expand Down
Expand Up @@ -160,11 +160,24 @@ export default {
options: ['focus', 'manual']
},
direction: {
control: 'select',
control: 'radio',
options: ['top', 'bottom']
},
align: {
control: 'radio',
options: ['start', 'end']
},
width: {
control: 'text'
control: {
type: 'radio',
options: [null, '100px', '480px', 'size-4600']
}
},
menuWidth: {
control: {
type: 'radio',
options: [null, '100px', '480px', 'size-4600']
}
}
}
} as ComponentMeta<typeof SearchAutocomplete>;
Expand Down
15 changes: 11 additions & 4 deletions packages/@react-spectrum/combobox/src/ComboBox.tsx
Expand Up @@ -14,6 +14,7 @@ import {AriaButtonProps} from '@react-types/button';
import ChevronDownMedium from '@spectrum-icons/ui/ChevronDownMedium';
import {
classNames,
dimensionValue,
useFocusableRef,
useIsMobileDevice,
useResizeObserver,
Expand Down Expand Up @@ -73,10 +74,12 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
menuTrigger = 'input',
shouldFlip = true,
direction = 'bottom',
align = 'end',
isQuiet,
loadingState,
onLoadMore,
allowsCustomValue,
menuWidth: customMenuWidth,
name,
formValue = 'text'
} = props;
Expand All @@ -92,6 +95,8 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
let unwrappedButtonRef = useUnwrapDOMRef(buttonRef);
let listBoxRef = useRef();
let inputRef = useRef<HTMLInputElement>();
// serve as the new popover `triggerRef` instead of `unwrappedButtonRef` before for better positioning.
let inputGroupRef = useRef<HTMLDivElement>();
let domRef = useFocusableRef(ref, inputRef);

let {contains} = useFilter({sensitivity: 'base'});
Expand Down Expand Up @@ -137,8 +142,9 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr

useLayoutEffect(onResize, [scale, onResize]);

let width = isQuiet ? null : menuWidth;
let style = {
width: isQuiet ? null : menuWidth,
width: customMenuWidth ? dimensionValue(customMenuWidth) : width,
minWidth: isQuiet ? `calc(${menuWidth}px + calc(2 * var(--spectrum-dropdown-quiet-offset)))` : menuWidth
};

Expand All @@ -161,17 +167,18 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
inputRef={inputRef}
triggerProps={buttonProps}
triggerRef={buttonRef}
validationState={props.validationState || (isInvalid ? 'invalid' : null)} />
validationState={props.validationState || (isInvalid ? 'invalid' : null)}
ref={inputGroupRef} />
</Field>
{name && formValue === 'key' && <input type="hidden" name={name} value={state.selectedKey} />}
<Popover
state={state}
UNSAFE_style={style}
UNSAFE_className={classNames(styles, 'spectrum-InputGroup-popover', {'spectrum-InputGroup-popover--quiet': isQuiet})}
ref={popoverRef}
triggerRef={unwrappedButtonRef}
triggerRef={inputGroupRef}
scrollRef={listBoxRef}
placement={`${direction} end`}
placement={`${direction} ${align}`}
hideArrow
isNonModal
shouldFlip={shouldFlip}>
Expand Down
17 changes: 15 additions & 2 deletions packages/@react-spectrum/combobox/stories/ComboBox.stories.tsx
Expand Up @@ -183,14 +183,27 @@ export default {
options: ['focus', 'manual']
},
direction: {
control: 'select',
control: 'radio',
options: ['top', 'bottom']
},
align: {
control: 'radio',
options: ['start', 'end']
},
allowsCustomValue: {
control: 'boolean'
},
width: {
control: 'text'
control: {
type: 'radio',
options: [null, '100px', '480px', 'size-4600']
}
},
menuWidth: {
control: {
type: 'radio',
options: [null, '100px', '480px', 'size-4600']
}
}
}
} as ComponentMeta<typeof ComboBox>;
Expand Down
8 changes: 8 additions & 0 deletions packages/@react-spectrum/picker/stories/Picker.stories.tsx
Expand Up @@ -134,6 +134,14 @@ export default {
isQuiet: {
control: 'boolean'
},
direction: {
control: 'radio',
options: ['top', 'bottom']
},
align: {
control: 'radio',
options: ['start', 'end']
},
width: {
control: {
type: 'radio',
Expand Down
8 changes: 7 additions & 1 deletion packages/@react-types/autocomplete/src/index.d.ts
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {AriaLabelingProps, AsyncLoadable, CollectionBase, DOMProps, Key, LoadingState, SpectrumFieldValidation, SpectrumLabelableProps, SpectrumTextInputBase, StyleProps} from '@react-types/shared';
import {AriaLabelingProps, AsyncLoadable, CollectionBase, DimensionValue, DOMProps, Key, LoadingState, SpectrumFieldValidation, SpectrumLabelableProps, SpectrumTextInputBase, StyleProps} from '@react-types/shared';
import {AriaSearchFieldProps, SearchFieldProps} from '@react-types/searchfield';
import {MenuTriggerAction} from '@react-types/combobox';
import {ReactElement} from 'react';
Expand Down Expand Up @@ -54,6 +54,10 @@ export interface SpectrumSearchAutocompleteProps<T> extends SpectrumTextInputBas
menuTrigger?: MenuTriggerAction,
/** Whether the SearchAutocomplete should be displayed with a quiet style. */
isQuiet?: boolean,
/** Alignment of the menu relative to the input target.
* @default 'start'
*/
align?: 'start' | 'end',
/**
* Direction the menu will render relative to the SearchAutocomplete.
* @default 'bottom'
Expand All @@ -66,6 +70,8 @@ export interface SpectrumSearchAutocompleteProps<T> extends SpectrumTextInputBas
* @default true
*/
shouldFlip?: boolean,
/** Width of the menu. By default, matches width of the trigger. */
menuWidth?: DimensionValue,
onLoadMore?: () => void,
/** An icon to display at the start of the input. */
icon?: ReactElement | null
Expand Down
7 changes: 7 additions & 0 deletions packages/@react-types/combobox/src/index.d.ts
Expand Up @@ -14,6 +14,7 @@ import {
AriaLabelingProps,
AsyncLoadable,
CollectionBase,
DimensionValue,
DOMProps,
FocusableProps,
HelpTextProps,
Expand Down Expand Up @@ -80,6 +81,10 @@ export interface SpectrumComboBoxProps<T> extends SpectrumTextInputBase, Omit<Ar
menuTrigger?: MenuTriggerAction,
/** Whether the ComboBox should be displayed with a quiet style. */
isQuiet?: boolean,
/** Alignment of the menu relative to the input target.
* @default 'end'
*/
align?: 'start' | 'end',
/**
* Direction the menu will render relative to the ComboBox.
* @default 'bottom'
Expand All @@ -92,6 +97,8 @@ export interface SpectrumComboBoxProps<T> extends SpectrumTextInputBase, Omit<Ar
* @default true
*/
shouldFlip?: boolean,
/** Width of the menu. By default, matches width of the trigger. */
menuWidth?: DimensionValue,
/**
* Whether the text or key of the selected item is submitted as part of an HTML form.
* When `allowsCustomValue` is `true`, this option does not apply and the text is always submitted.
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-types/select/src/index.d.ts
Expand Up @@ -67,7 +67,7 @@ export interface SpectrumPickerProps<T> extends AriaSelectProps<T>, AsyncLoadabl
* @default true
*/
shouldFlip?: boolean,
/** Width of the menu. */
/** Width of the menu. By default, matches width of the trigger. */
menuWidth?: DimensionValue,
/** Whether the element should receive focus on render. */
autoFocus?: boolean
Expand Down