Skip to content

Commit dd8b226

Browse files
authored
Add menuWidth & expose align props in ComboBox and SearchAutocomplete (#5446)
* Add menuWidth & expose align props in ComboBox and SearchAutocomplete
1 parent ef33efe commit dd8b226

File tree

8 files changed

+70
-13
lines changed

8 files changed

+70
-13
lines changed

packages/@react-spectrum/autocomplete/src/SearchAutocomplete.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212
import {AriaButtonProps} from '@react-types/button';
13-
import {classNames, useFocusableRef, useIsMobileDevice, useResizeObserver, useUnwrapDOMRef} from '@react-spectrum/utils';
13+
import {classNames, dimensionValue, useFocusableRef, useIsMobileDevice, useResizeObserver, useUnwrapDOMRef} from '@react-spectrum/utils';
1414
import {ClearButton} from '@react-spectrum/button';
1515
import {DOMRefValue, FocusableRef} from '@react-types/shared';
1616
import {Field} from '@react-spectrum/label';
@@ -70,7 +70,9 @@ function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocomp
7070
menuTrigger = 'input',
7171
shouldFlip = true,
7272
direction = 'bottom',
73+
align = 'end',
7374
isQuiet,
75+
menuWidth: customMenuWidth,
7476
loadingState,
7577
onLoadMore,
7678
onSubmit = () => {},
@@ -130,8 +132,9 @@ function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocomp
130132

131133
useLayoutEffect(onResize, [scale, onResize]);
132134

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

@@ -161,7 +164,7 @@ function _SearchAutocompleteBase<T extends object>(props: SpectrumSearchAutocomp
161164
UNSAFE_className={classNames(styles, 'spectrum-InputGroup-popover', {'spectrum-InputGroup-popover--quiet': isQuiet})}
162165
ref={popoverRef}
163166
triggerRef={inputRef}
164-
placement={`${direction} end`}
167+
placement={`${direction} ${align}`}
165168
hideArrow
166169
isNonModal
167170
shouldFlip={shouldFlip}>

packages/@react-spectrum/autocomplete/stories/SearchAutocomplete.stories.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,24 @@ export default {
160160
options: ['focus', 'manual']
161161
},
162162
direction: {
163-
control: 'select',
163+
control: 'radio',
164164
options: ['top', 'bottom']
165165
},
166+
align: {
167+
control: 'radio',
168+
options: ['start', 'end']
169+
},
166170
width: {
167-
control: 'text'
171+
control: {
172+
type: 'radio',
173+
options: [null, '100px', '480px', 'size-4600']
174+
}
175+
},
176+
menuWidth: {
177+
control: {
178+
type: 'radio',
179+
options: [null, '100px', '480px', 'size-4600']
180+
}
168181
}
169182
}
170183
} as ComponentMeta<typeof SearchAutocomplete>;

packages/@react-spectrum/combobox/src/ComboBox.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {AriaButtonProps} from '@react-types/button';
1414
import ChevronDownMedium from '@spectrum-icons/ui/ChevronDownMedium';
1515
import {
1616
classNames,
17+
dimensionValue,
1718
useFocusableRef,
1819
useIsMobileDevice,
1920
useResizeObserver,
@@ -73,10 +74,12 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
7374
menuTrigger = 'input',
7475
shouldFlip = true,
7576
direction = 'bottom',
77+
align = 'end',
7678
isQuiet,
7779
loadingState,
7880
onLoadMore,
7981
allowsCustomValue,
82+
menuWidth: customMenuWidth,
8083
name,
8184
formValue = 'text'
8285
} = props;
@@ -92,6 +95,8 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
9295
let unwrappedButtonRef = useUnwrapDOMRef(buttonRef);
9396
let listBoxRef = useRef();
9497
let inputRef = useRef<HTMLInputElement>();
98+
// serve as the new popover `triggerRef` instead of `unwrappedButtonRef` before for better positioning.
99+
let inputGroupRef = useRef<HTMLDivElement>();
95100
let domRef = useFocusableRef(ref, inputRef);
96101

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

138143
useLayoutEffect(onResize, [scale, onResize]);
139144

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

@@ -161,17 +167,18 @@ const ComboBoxBase = React.forwardRef(function ComboBoxBase<T extends object>(pr
161167
inputRef={inputRef}
162168
triggerProps={buttonProps}
163169
triggerRef={buttonRef}
164-
validationState={props.validationState || (isInvalid ? 'invalid' : null)} />
170+
validationState={props.validationState || (isInvalid ? 'invalid' : null)}
171+
ref={inputGroupRef} />
165172
</Field>
166173
{name && formValue === 'key' && <input type="hidden" name={name} value={state.selectedKey} />}
167174
<Popover
168175
state={state}
169176
UNSAFE_style={style}
170177
UNSAFE_className={classNames(styles, 'spectrum-InputGroup-popover', {'spectrum-InputGroup-popover--quiet': isQuiet})}
171178
ref={popoverRef}
172-
triggerRef={unwrappedButtonRef}
179+
triggerRef={inputGroupRef}
173180
scrollRef={listBoxRef}
174-
placement={`${direction} end`}
181+
placement={`${direction} ${align}`}
175182
hideArrow
176183
isNonModal
177184
shouldFlip={shouldFlip}>

packages/@react-spectrum/combobox/stories/ComboBox.stories.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,27 @@ export default {
183183
options: ['focus', 'manual']
184184
},
185185
direction: {
186-
control: 'select',
186+
control: 'radio',
187187
options: ['top', 'bottom']
188188
},
189+
align: {
190+
control: 'radio',
191+
options: ['start', 'end']
192+
},
189193
allowsCustomValue: {
190194
control: 'boolean'
191195
},
192196
width: {
193-
control: 'text'
197+
control: {
198+
type: 'radio',
199+
options: [null, '100px', '480px', 'size-4600']
200+
}
201+
},
202+
menuWidth: {
203+
control: {
204+
type: 'radio',
205+
options: [null, '100px', '480px', 'size-4600']
206+
}
194207
}
195208
}
196209
} as ComponentMeta<typeof ComboBox>;

packages/@react-spectrum/picker/stories/Picker.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ export default {
134134
isQuiet: {
135135
control: 'boolean'
136136
},
137+
direction: {
138+
control: 'radio',
139+
options: ['top', 'bottom']
140+
},
141+
align: {
142+
control: 'radio',
143+
options: ['start', 'end']
144+
},
137145
width: {
138146
control: {
139147
type: 'radio',

packages/@react-types/autocomplete/src/index.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {AriaLabelingProps, AsyncLoadable, CollectionBase, DOMProps, Key, LoadingState, SpectrumFieldValidation, SpectrumLabelableProps, SpectrumTextInputBase, StyleProps} from '@react-types/shared';
13+
import {AriaLabelingProps, AsyncLoadable, CollectionBase, DimensionValue, DOMProps, Key, LoadingState, SpectrumFieldValidation, SpectrumLabelableProps, SpectrumTextInputBase, StyleProps} from '@react-types/shared';
1414
import {AriaSearchFieldProps, SearchFieldProps} from '@react-types/searchfield';
1515
import {MenuTriggerAction} from '@react-types/combobox';
1616
import {ReactElement} from 'react';
@@ -54,6 +54,10 @@ export interface SpectrumSearchAutocompleteProps<T> extends SpectrumTextInputBas
5454
menuTrigger?: MenuTriggerAction,
5555
/** Whether the SearchAutocomplete should be displayed with a quiet style. */
5656
isQuiet?: boolean,
57+
/** Alignment of the menu relative to the input target.
58+
* @default 'start'
59+
*/
60+
align?: 'start' | 'end',
5761
/**
5862
* Direction the menu will render relative to the SearchAutocomplete.
5963
* @default 'bottom'
@@ -66,6 +70,8 @@ export interface SpectrumSearchAutocompleteProps<T> extends SpectrumTextInputBas
6670
* @default true
6771
*/
6872
shouldFlip?: boolean,
73+
/** Width of the menu. By default, matches width of the trigger. */
74+
menuWidth?: DimensionValue,
6975
onLoadMore?: () => void,
7076
/** An icon to display at the start of the input. */
7177
icon?: ReactElement | null

packages/@react-types/combobox/src/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
AriaLabelingProps,
1515
AsyncLoadable,
1616
CollectionBase,
17+
DimensionValue,
1718
DOMProps,
1819
FocusableProps,
1920
HelpTextProps,
@@ -80,6 +81,10 @@ export interface SpectrumComboBoxProps<T> extends SpectrumTextInputBase, Omit<Ar
8081
menuTrigger?: MenuTriggerAction,
8182
/** Whether the ComboBox should be displayed with a quiet style. */
8283
isQuiet?: boolean,
84+
/** Alignment of the menu relative to the input target.
85+
* @default 'end'
86+
*/
87+
align?: 'start' | 'end',
8388
/**
8489
* Direction the menu will render relative to the ComboBox.
8590
* @default 'bottom'
@@ -92,6 +97,8 @@ export interface SpectrumComboBoxProps<T> extends SpectrumTextInputBase, Omit<Ar
9297
* @default true
9398
*/
9499
shouldFlip?: boolean,
100+
/** Width of the menu. By default, matches width of the trigger. */
101+
menuWidth?: DimensionValue,
95102
/**
96103
* Whether the text or key of the selected item is submitted as part of an HTML form.
97104
* When `allowsCustomValue` is `true`, this option does not apply and the text is always submitted.

packages/@react-types/select/src/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export interface SpectrumPickerProps<T> extends AriaSelectProps<T>, AsyncLoadabl
6767
* @default true
6868
*/
6969
shouldFlip?: boolean,
70-
/** Width of the menu. */
70+
/** Width of the menu. By default, matches width of the trigger. */
7171
menuWidth?: DimensionValue,
7272
/** Whether the element should receive focus on render. */
7373
autoFocus?: boolean

0 commit comments

Comments
 (0)