Skip to content

Commit 7dc34df

Browse files
feat(Listbox): add in FluidComboBox, FluidMultiSelect (#12163)
* 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>
1 parent 5e676fd commit 7dc34df

File tree

24 files changed

+1587
-9
lines changed

24 files changed

+1587
-9
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright IBM Corp. 2022
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
const { expect, test } = require('@playwright/test');
11+
const { themes } = require('../../test-utils/env');
12+
const { snapshotStory, visitStory } = require('../../test-utils/storybook');
13+
14+
test.describe('FluidComboBox', () => {
15+
themes.forEach((theme) => {
16+
test.describe(theme, () => {
17+
test('fluid dropdown @vrt', async ({ page }) => {
18+
await snapshotStory(page, {
19+
component: 'FluidComboBox',
20+
id: 'experimental-unstable-fluidcombobox--default',
21+
theme,
22+
});
23+
});
24+
});
25+
});
26+
27+
test('accessibility-checker @avt', async ({ page }) => {
28+
await visitStory(page, {
29+
component: 'FluidComboBox',
30+
id: 'experimental-unstable-fluidcombobox--default',
31+
globals: {
32+
theme: 'white',
33+
},
34+
});
35+
await expect(page).toHaveNoACViolations('FluidComboBox');
36+
});
37+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright IBM Corp. 2022
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
const { expect, test } = require('@playwright/test');
11+
const { themes } = require('../../test-utils/env');
12+
const { snapshotStory, visitStory } = require('../../test-utils/storybook');
13+
14+
test.describe('FluidMultiSelect', () => {
15+
themes.forEach((theme) => {
16+
test.describe(theme, () => {
17+
test('fluid dropdown @vrt', async ({ page }) => {
18+
await snapshotStory(page, {
19+
component: 'FluidMultiSelect',
20+
id: 'experimental-unstable-fluidmultiselect--default',
21+
theme,
22+
});
23+
});
24+
});
25+
});
26+
27+
test('accessibility-checker @avt', async ({ page }) => {
28+
await visitStory(page, {
29+
component: 'FluidMultiSelect',
30+
id: 'experimental-unstable-fluidmultiselect--default',
31+
globals: {
32+
theme: 'white',
33+
},
34+
});
35+
await expect(page).toHaveNoACViolations('FluidMultiSelect');
36+
});
37+
});

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9295,6 +9295,111 @@ Map {
92959295
},
92969296
},
92979297
},
9298+
"unstable__FluidComboBox" => Object {
9299+
"$$typeof": Symbol(react.forward_ref),
9300+
"propTypes": Object {
9301+
"className": Object {
9302+
"type": "string",
9303+
},
9304+
"direction": Object {
9305+
"args": Array [
9306+
Array [
9307+
"top",
9308+
"bottom",
9309+
],
9310+
],
9311+
"type": "oneOf",
9312+
},
9313+
"disabled": Object {
9314+
"type": "bool",
9315+
},
9316+
"id": Object {
9317+
"isRequired": true,
9318+
"type": "string",
9319+
},
9320+
"initialSelectedItem": Object {
9321+
"args": Array [
9322+
Array [
9323+
Object {
9324+
"type": "object",
9325+
},
9326+
Object {
9327+
"type": "string",
9328+
},
9329+
Object {
9330+
"type": "number",
9331+
},
9332+
],
9333+
],
9334+
"type": "oneOfType",
9335+
},
9336+
"invalid": Object {
9337+
"type": "bool",
9338+
},
9339+
"invalidText": Object {
9340+
"type": "node",
9341+
},
9342+
"isCondensed": Object {
9343+
"type": "bool",
9344+
},
9345+
"itemToElement": Object {
9346+
"type": "func",
9347+
},
9348+
"itemToString": Object {
9349+
"type": "func",
9350+
},
9351+
"items": Object {
9352+
"isRequired": true,
9353+
"type": "array",
9354+
},
9355+
"label": Object {
9356+
"isRequired": true,
9357+
"type": "node",
9358+
},
9359+
"onChange": Object {
9360+
"type": "func",
9361+
},
9362+
"renderSelectedItem": Object {
9363+
"type": "func",
9364+
},
9365+
"selectedItem": Object {
9366+
"args": Array [
9367+
Array [
9368+
Object {
9369+
"type": "object",
9370+
},
9371+
Object {
9372+
"type": "string",
9373+
},
9374+
Object {
9375+
"type": "number",
9376+
},
9377+
],
9378+
],
9379+
"type": "oneOfType",
9380+
},
9381+
"titleText": Object {
9382+
"type": "node",
9383+
},
9384+
"translateWithId": Object {
9385+
"type": "func",
9386+
},
9387+
"warn": Object {
9388+
"type": "bool",
9389+
},
9390+
"warnText": Object {
9391+
"type": "node",
9392+
},
9393+
},
9394+
"render": [Function],
9395+
},
9396+
"unstable__FluidComboBoxSkeleton" => Object {
9397+
"propTypes": Object {
9398+
"className": Object {
9399+
"type": "string",
9400+
},
9401+
},
9402+
},
92989403
"unstable__FluidDropdown" => Object {
92999404
"$$typeof": Symbol(react.forward_ref),
93009405
"propTypes": Object {
@@ -9400,6 +9505,114 @@ Map {
94009505
},
94019506
},
94029507
},
9508+
"unstable__FluidMultiSelect" => Object {
9509+
"$$typeof": Symbol(react.forward_ref),
9510+
"propTypes": Object {
9511+
"className": Object {
9512+
"type": "string",
9513+
},
9514+
"direction": Object {
9515+
"args": Array [
9516+
Array [
9517+
"top",
9518+
"bottom",
9519+
],
9520+
],
9521+
"type": "oneOf",
9522+
},
9523+
"disabled": Object {
9524+
"type": "bool",
9525+
},
9526+
"id": Object {
9527+
"isRequired": true,
9528+
"type": "string",
9529+
},
9530+
"initialSelectedItem": Object {
9531+
"args": Array [
9532+
Array [
9533+
Object {
9534+
"type": "object",
9535+
},
9536+
Object {
9537+
"type": "string",
9538+
},
9539+
Object {
9540+
"type": "number",
9541+
},
9542+
],
9543+
],
9544+
"type": "oneOfType",
9545+
},
9546+
"invalid": Object {
9547+
"type": "bool",
9548+
},
9549+
"invalidText": Object {
9550+
"type": "node",
9551+
},
9552+
"isCondensed": Object {
9553+
"type": "bool",
9554+
},
9555+
"isFilterable": Object {
9556+
"type": "bool",
9557+
},
9558+
"itemToElement": Object {
9559+
"type": "func",
9560+
},
9561+
"itemToString": Object {
9562+
"type": "func",
9563+
},
9564+
"items": Object {
9565+
"isRequired": true,
9566+
"type": "array",
9567+
},
9568+
"label": Object {
9569+
"isRequired": true,
9570+
"type": "node",
9571+
},
9572+
"onChange": Object {
9573+
"type": "func",
9574+
},
9575+
"renderSelectedItem": Object {
9576+
"type": "func",
9577+
},
9578+
"selectedItem": Object {
9579+
"args": Array [
9580+
Array [
9581+
Object {
9582+
"type": "object",
9583+
},
9584+
Object {
9585+
"type": "string",
9586+
},
9587+
Object {
9588+
"type": "number",
9589+
},
9590+
],
9591+
],
9592+
"type": "oneOfType",
9593+
},
9594+
"titleText": Object {
9595+
"type": "node",
9596+
},
9597+
"translateWithId": Object {
9598+
"type": "func",
9599+
},
9600+
"warn": Object {
9601+
"type": "bool",
9602+
},
9603+
"warnText": Object {
9604+
"type": "node",
9605+
},
9606+
},
9607+
"render": [Function],
9608+
},
9609+
"unstable__FluidMultiSelectSkeleton" => Object {
9610+
"propTypes": Object {
9611+
"className": Object {
9612+
"type": "string",
9613+
},
9614+
},
9615+
},
94039616
"unstable__FluidSelect" => Object {
94049617
"$$typeof": Symbol(react.forward_ref),
94059618
"propTypes": Object {

packages/react/src/__tests__/index-test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,12 @@ describe('Carbon Components React', () => {
229229
"unstable_Pagination",
230230
"unstable_Text",
231231
"unstable_TextDirection",
232+
"unstable__FluidComboBox",
233+
"unstable__FluidComboBoxSkeleton",
232234
"unstable__FluidDropdown",
233235
"unstable__FluidDropdownSkeleton",
236+
"unstable__FluidMultiSelect",
237+
"unstable__FluidMultiSelectSkeleton",
234238
"unstable__FluidSelect",
235239
"unstable__FluidSelectSkeleton",
236240
"unstable__FluidTextArea",

packages/react/src/components/ComboBox/ComboBox.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import cx from 'classnames';
99
import Downshift from 'downshift';
1010
import PropTypes from 'prop-types';
11-
import React, { useEffect, useState, useRef } from 'react';
11+
import React, { useContext, useEffect, useState, useRef } from 'react';
1212
import { Text } from '../Text';
1313
import {
1414
Checkmark,
@@ -23,6 +23,7 @@ import mergeRefs from '../../tools/mergeRefs';
2323
import { useFeatureFlag } from '../FeatureFlags';
2424
import deprecate from '../../prop-types/deprecate';
2525
import { usePrefix } from '../../internal/usePrefix';
26+
import { FormContext } from '../FluidForm';
2627

2728
const defaultItemToString = (item) => {
2829
if (typeof item === 'string') {
@@ -102,7 +103,7 @@ const ComboBox = React.forwardRef((props, ref) => {
102103
...rest
103104
} = props;
104105
const prefix = usePrefix();
105-
106+
const { isFluid } = useContext(FormContext);
106107
const textInput = useRef();
107108
const comboBoxInstanceId = getInstanceId();
108109
const [inputValue, setInputValue] = useState(
@@ -113,6 +114,7 @@ const ComboBox = React.forwardRef((props, ref) => {
113114
selectedItem,
114115
})
115116
);
117+
const [isFocused, setIsFocused] = useState(false);
116118
const [prevSelectedItem, setPrevSelectedItem] = useState(null);
117119
const [doneInitialSelectedItem, setDoneInitialSelectedItem] = useState(null);
118120
const savedOnInputChange = useRef(onInputChange);
@@ -214,6 +216,10 @@ const ComboBox = React.forwardRef((props, ref) => {
214216
});
215217
const wrapperClasses = cx(`${prefix}--list-box__wrapper`, [
216218
enabled ? containerClassName : null,
219+
{
220+
[`${prefix}--list-box__wrapper--fluid--invalid`]: isFluid && invalid,
221+
[`${prefix}--list-box__wrapper--fluid--focus`]: isFluid && isFocused,
222+
},
217223
]);
218224

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

303+
const handleFocus = (evt) => {
304+
if (evt.target.type === 'button') {
305+
setIsFocused(false);
306+
} else {
307+
setIsFocused(evt.type === 'focus' ? true : false);
308+
}
309+
};
310+
297311
return (
298312
<div className={wrapperClasses}>
299313
{titleText && (
@@ -302,6 +316,8 @@ const ComboBox = React.forwardRef((props, ref) => {
302316
</Text>
303317
)}
304318
<ListBox
319+
onFocus={handleFocus}
320+
onBlur={handleFocus}
305321
className={className}
306322
disabled={disabled}
307323
invalid={invalid}
@@ -394,7 +410,7 @@ const ComboBox = React.forwardRef((props, ref) => {
394410
: null}
395411
</ListBox.Menu>
396412
</ListBox>
397-
{helperText && !invalid && !warn && (
413+
{helperText && !invalid && !warn && !isFluid && (
398414
<Text as="div" id={comboBoxHelperId} className={helperClasses}>
399415
{helperText}
400416
</Text>

0 commit comments

Comments
 (0)