Skip to content

Commit

Permalink
feat(Listbox): add in FluidComboBox, FluidMultiSelect (#12163)
Browse files Browse the repository at this point in the history
* feat(FluidDropdown): initial creation and styles

* docs(FluidDropdown): update PropType definitions

* test(FluidDropdown): add e2e, api tests

* chore(snapshot): update snapshots

* feat(FluidComboBox): add FluidComboBox

* test(FluidComboBox): add e2e tests

* feat(FluidMultiSelect): add FluidMultiSelect

* test(FluidDropdown): remove console log)

* feat(skeleton): add FluidMultiSelectSkeleton, FluidComboBoxSkeleton

* test(snapshot): update snapshots

* test(e2e): rename multiselect test

* fix(ListBox): make style changes based on feedback

* fix(ListBox): workaround for adjacent elements in focus, invalid state

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
tw15egan and kodiakhq[bot] authored Oct 21, 2022
1 parent 5e676fd commit 7dc34df
Show file tree
Hide file tree
Showing 24 changed files with 1,587 additions and 9 deletions.
37 changes: 37 additions & 0 deletions e2e/components/FluidComboBox/FluidComboBox-test.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright IBM Corp. 2022
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const { expect, test } = require('@playwright/test');
const { themes } = require('../../test-utils/env');
const { snapshotStory, visitStory } = require('../../test-utils/storybook');

test.describe('FluidComboBox', () => {
themes.forEach((theme) => {
test.describe(theme, () => {
test('fluid dropdown @vrt', async ({ page }) => {
await snapshotStory(page, {
component: 'FluidComboBox',
id: 'experimental-unstable-fluidcombobox--default',
theme,
});
});
});
});

test('accessibility-checker @avt', async ({ page }) => {
await visitStory(page, {
component: 'FluidComboBox',
id: 'experimental-unstable-fluidcombobox--default',
globals: {
theme: 'white',
},
});
await expect(page).toHaveNoACViolations('FluidComboBox');
});
});
37 changes: 37 additions & 0 deletions e2e/components/FluidMultiSelect/FluidMultiSelect-test.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright IBM Corp. 2022
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const { expect, test } = require('@playwright/test');
const { themes } = require('../../test-utils/env');
const { snapshotStory, visitStory } = require('../../test-utils/storybook');

test.describe('FluidMultiSelect', () => {
themes.forEach((theme) => {
test.describe(theme, () => {
test('fluid dropdown @vrt', async ({ page }) => {
await snapshotStory(page, {
component: 'FluidMultiSelect',
id: 'experimental-unstable-fluidmultiselect--default',
theme,
});
});
});
});

test('accessibility-checker @avt', async ({ page }) => {
await visitStory(page, {
component: 'FluidMultiSelect',
id: 'experimental-unstable-fluidmultiselect--default',
globals: {
theme: 'white',
},
});
await expect(page).toHaveNoACViolations('FluidMultiSelect');
});
});
213 changes: 213 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9295,6 +9295,111 @@ Map {
},
},
},
"unstable__FluidComboBox" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"className": Object {
"type": "string",
},
"direction": Object {
"args": Array [
Array [
"top",
"bottom",
],
],
"type": "oneOf",
},
"disabled": Object {
"type": "bool",
},
"id": Object {
"isRequired": true,
"type": "string",
},
"initialSelectedItem": Object {
"args": Array [
Array [
Object {
"type": "object",
},
Object {
"type": "string",
},
Object {
"type": "number",
},
],
],
"type": "oneOfType",
},
"invalid": Object {
"type": "bool",
},
"invalidText": Object {
"type": "node",
},
"isCondensed": Object {
"type": "bool",
},
"itemToElement": Object {
"type": "func",
},
"itemToString": Object {
"type": "func",
},
"items": Object {
"isRequired": true,
"type": "array",
},
"label": Object {
"isRequired": true,
"type": "node",
},
"onChange": Object {
"type": "func",
},
"renderSelectedItem": Object {
"type": "func",
},
"selectedItem": Object {
"args": Array [
Array [
Object {
"type": "object",
},
Object {
"type": "string",
},
Object {
"type": "number",
},
],
],
"type": "oneOfType",
},
"titleText": Object {
"type": "node",
},
"translateWithId": Object {
"type": "func",
},
"warn": Object {
"type": "bool",
},
"warnText": Object {
"type": "node",
},
},
"render": [Function],
},
"unstable__FluidComboBoxSkeleton" => Object {
"propTypes": Object {
"className": Object {
"type": "string",
},
},
},
"unstable__FluidDropdown" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
Expand Down Expand Up @@ -9400,6 +9505,114 @@ Map {
},
},
},
"unstable__FluidMultiSelect" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"className": Object {
"type": "string",
},
"direction": Object {
"args": Array [
Array [
"top",
"bottom",
],
],
"type": "oneOf",
},
"disabled": Object {
"type": "bool",
},
"id": Object {
"isRequired": true,
"type": "string",
},
"initialSelectedItem": Object {
"args": Array [
Array [
Object {
"type": "object",
},
Object {
"type": "string",
},
Object {
"type": "number",
},
],
],
"type": "oneOfType",
},
"invalid": Object {
"type": "bool",
},
"invalidText": Object {
"type": "node",
},
"isCondensed": Object {
"type": "bool",
},
"isFilterable": Object {
"type": "bool",
},
"itemToElement": Object {
"type": "func",
},
"itemToString": Object {
"type": "func",
},
"items": Object {
"isRequired": true,
"type": "array",
},
"label": Object {
"isRequired": true,
"type": "node",
},
"onChange": Object {
"type": "func",
},
"renderSelectedItem": Object {
"type": "func",
},
"selectedItem": Object {
"args": Array [
Array [
Object {
"type": "object",
},
Object {
"type": "string",
},
Object {
"type": "number",
},
],
],
"type": "oneOfType",
},
"titleText": Object {
"type": "node",
},
"translateWithId": Object {
"type": "func",
},
"warn": Object {
"type": "bool",
},
"warnText": Object {
"type": "node",
},
},
"render": [Function],
},
"unstable__FluidMultiSelectSkeleton" => Object {
"propTypes": Object {
"className": Object {
"type": "string",
},
},
},
"unstable__FluidSelect" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,12 @@ describe('Carbon Components React', () => {
"unstable_Pagination",
"unstable_Text",
"unstable_TextDirection",
"unstable__FluidComboBox",
"unstable__FluidComboBoxSkeleton",
"unstable__FluidDropdown",
"unstable__FluidDropdownSkeleton",
"unstable__FluidMultiSelect",
"unstable__FluidMultiSelectSkeleton",
"unstable__FluidSelect",
"unstable__FluidSelectSkeleton",
"unstable__FluidTextArea",
Expand Down
22 changes: 19 additions & 3 deletions packages/react/src/components/ComboBox/ComboBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import cx from 'classnames';
import Downshift from 'downshift';
import PropTypes from 'prop-types';
import React, { useEffect, useState, useRef } from 'react';
import React, { useContext, useEffect, useState, useRef } from 'react';
import { Text } from '../Text';
import {
Checkmark,
Expand All @@ -23,6 +23,7 @@ import mergeRefs from '../../tools/mergeRefs';
import { useFeatureFlag } from '../FeatureFlags';
import deprecate from '../../prop-types/deprecate';
import { usePrefix } from '../../internal/usePrefix';
import { FormContext } from '../FluidForm';

const defaultItemToString = (item) => {
if (typeof item === 'string') {
Expand Down Expand Up @@ -102,7 +103,7 @@ const ComboBox = React.forwardRef((props, ref) => {
...rest
} = props;
const prefix = usePrefix();

const { isFluid } = useContext(FormContext);
const textInput = useRef();
const comboBoxInstanceId = getInstanceId();
const [inputValue, setInputValue] = useState(
Expand All @@ -113,6 +114,7 @@ const ComboBox = React.forwardRef((props, ref) => {
selectedItem,
})
);
const [isFocused, setIsFocused] = useState(false);
const [prevSelectedItem, setPrevSelectedItem] = useState(null);
const [doneInitialSelectedItem, setDoneInitialSelectedItem] = useState(null);
const savedOnInputChange = useRef(onInputChange);
Expand Down Expand Up @@ -214,6 +216,10 @@ const ComboBox = React.forwardRef((props, ref) => {
});
const wrapperClasses = cx(`${prefix}--list-box__wrapper`, [
enabled ? containerClassName : null,
{
[`${prefix}--list-box__wrapper--fluid--invalid`]: isFluid && invalid,
[`${prefix}--list-box__wrapper--fluid--focus`]: isFluid && isFocused,
},
]);

const inputClasses = cx(`${prefix}--text-input`, {
Expand Down Expand Up @@ -294,6 +300,14 @@ const ComboBox = React.forwardRef((props, ref) => {
},
});

const handleFocus = (evt) => {
if (evt.target.type === 'button') {
setIsFocused(false);
} else {
setIsFocused(evt.type === 'focus' ? true : false);
}
};

return (
<div className={wrapperClasses}>
{titleText && (
Expand All @@ -302,6 +316,8 @@ const ComboBox = React.forwardRef((props, ref) => {
</Text>
)}
<ListBox
onFocus={handleFocus}
onBlur={handleFocus}
className={className}
disabled={disabled}
invalid={invalid}
Expand Down Expand Up @@ -394,7 +410,7 @@ const ComboBox = React.forwardRef((props, ref) => {
: null}
</ListBox.Menu>
</ListBox>
{helperText && !invalid && !warn && (
{helperText && !invalid && !warn && !isFluid && (
<Text as="div" id={comboBoxHelperId} className={helperClasses}>
{helperText}
</Text>
Expand Down
Loading

0 comments on commit 7dc34df

Please sign in to comment.