diff --git a/packages/dropdowns.next/.size-snapshot.json b/packages/dropdowns.next/.size-snapshot.json index 59c5e80d9e7..b71158ed689 100644 --- a/packages/dropdowns.next/.size-snapshot.json +++ b/packages/dropdowns.next/.size-snapshot.json @@ -1,20 +1,20 @@ { "index.cjs.js": { - "bundled": 55344, - "minified": 40274, - "gzipped": 9098 + "bundled": 55890, + "minified": 40577, + "gzipped": 9273 }, "index.esm.js": { - "bundled": 50695, - "minified": 35869, - "gzipped": 8627, + "bundled": 51241, + "minified": 36172, + "gzipped": 8697, "treeshaked": { "rollup": { - "code": 28209, + "code": 28490, "import_statements": 1064 }, "webpack": { - "code": 31080 + "code": 31363 } } } diff --git a/packages/dropdowns.next/src/elements/combobox/Combobox.spec.tsx b/packages/dropdowns.next/src/elements/combobox/Combobox.spec.tsx index 843ee001541..85049f567f2 100644 --- a/packages/dropdowns.next/src/elements/combobox/Combobox.spec.tsx +++ b/packages/dropdowns.next/src/elements/combobox/Combobox.spec.tsx @@ -220,10 +220,14 @@ describe('Combobox', () => { it('renders non-editable as expected', () => { const { getByTestId } = render(); + const combobox = getByTestId('combobox'); const input = getByTestId('input'); + expect(combobox).toHaveAttribute('tabIndex', '-1'); expect(input).toHaveAttribute('readonly'); expect(input).toHaveAttribute('hidden'); + expect(input).toHaveAttribute('aria-hidden', 'true'); + expect(input).toHaveStyleRule('display', 'none', { modifier: '&[aria-hidden="true"]' }); }); it('renders `isMultiselectable` as expected', () => { @@ -349,6 +353,41 @@ describe('Combobox', () => { expect(button).toHaveTextContent('test-1'); }); + it('handles tag group expansion as expected', async () => { + const tagProps = { 'data-test-id': 'tag' } as HTMLAttributes; + const { getByTestId } = render( + + + ); + const combobox = getByTestId('combobox'); + const trigger = combobox.firstChild as HTMLElement; + const input = getByTestId('input'); + const tag = getByTestId('tag'); + const button = tag.nextSibling as HTMLElement; + + expect(tag).toHaveAttribute('hidden'); + expect(button).not.toHaveAttribute('hidden'); + + await user.click(button); + + expect(tag).not.toHaveAttribute('hidden'); + expect(button).toHaveAttribute('hidden'); + expect(input).toHaveFocus(); + + await user.keyboard('{Tab}'); + + expect(tag).toHaveAttribute('hidden'); + expect(button).not.toHaveAttribute('hidden'); + + await user.click(trigger); + + expect(tag).not.toHaveAttribute('hidden'); + expect(button).toHaveAttribute('hidden'); + expect(input).toHaveFocus(); + }); + it('handles `renderValue` as expected', () => { const { getByTestId } = render( `test-${(selection as ISelectedOption).value}`}> diff --git a/packages/dropdowns.next/src/elements/combobox/Combobox.tsx b/packages/dropdowns.next/src/elements/combobox/Combobox.tsx index 7c369a62c28..63e57a80973 100644 --- a/packages/dropdowns.next/src/elements/combobox/Combobox.tsx +++ b/packages/dropdowns.next/src/elements/combobox/Combobox.tsx @@ -18,7 +18,7 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; import { ThemeContext } from 'styled-components'; -import { IOption, IUseComboboxReturnValue, useCombobox } from '@zendeskgarden/container-combobox'; +import { IUseComboboxReturnValue, useCombobox } from '@zendeskgarden/container-combobox'; import { DEFAULT_THEME, useText, useWindow } from '@zendeskgarden/react-theming'; import { VALIDATION } from '@zendeskgarden/react-forms'; import ChevronIcon from '@zendeskgarden/svg-icons/src/16/chevron-down-stroke.svg'; @@ -31,13 +31,13 @@ import { StyledInputIcon, StyledInput, StyledInputGroup, + StyledTagsButton, StyledTrigger, StyledValue } from '../../views'; -import { StyledTagsButton } from '../../views/combobox/StyledTagsButton'; import { Listbox } from './Listbox'; -import { Tag } from './Tag'; -import { toOptions, toString } from './utils'; +import { TagGroup } from './TagGroup'; +import { toOptions } from './utils'; const MAX_TAGS = 4; @@ -82,6 +82,7 @@ export const Combobox = forwardRef( ) => { const { hasHint, hasMessage, labelProps, setLabelProps } = useFieldContext(); const [isLabelHovered, setIsLabelHovered] = useState(false); + const [isTagGroupExpanded, setIsTagGroupExpanded] = useState(false); const [optionTagProps, setOptionTagProps] = useState>( {} ); @@ -99,7 +100,6 @@ export const Combobox = forwardRef( const triggerRef = useRef(null); const inputRef = useRef(null); const listboxRef = useRef(null); - const tagsButtonRef = useRef(null); /* istanbul ignore next */ const theme = useContext(ThemeContext) || DEFAULT_THEME; const environment = useWindow(theme); @@ -178,10 +178,18 @@ export const Combobox = forwardRef( ...(getTriggerProps({ onFocus: () => { hasFocus.current = true; + + if (isMultiselectable) { + setIsTagGroupExpanded(true); + } }, onBlur: event => { if (event.relatedTarget === null || !triggerRef.current?.contains(event.relatedTarget)) { hasFocus.current = false; + + if (isMultiselectable) { + setIsTagGroupExpanded(false); + } } } }) as HTMLAttributes) @@ -191,6 +199,7 @@ export const Combobox = forwardRef( hidden: !(isEditable && hasFocus.current), isBare, isCompact, + isEditable, isMultiselectable, placeholder, ...(getInputProps({ @@ -215,47 +224,14 @@ export const Combobox = forwardRef( return () => labelProps && setLabelProps(undefined); }, [getLabelProps, labelProps, setLabelProps]); - const Tags = ({ selectedOptions }: { selectedOptions: IOption[] }) => { - const value = selectedOptions.length - maxTags; - - return ( - <> - {selectedOptions.map((option, index) => { - const key = toString(option); - const disabled = isDisabled || option.disabled; - const hidden = !hasFocus.current && index >= maxTags; - - return ( -