Skip to content

Commit 0be4c21

Browse files
authored
Merge branch 'master' into fix-1190
2 parents 648ec27 + d0735cc commit 0be4c21

File tree

5 files changed

+65
-49
lines changed

5 files changed

+65
-49
lines changed

lib/src/select/Listbox.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useLayoutEffect, useRef } from "react";
1+
import React, { useState, useLayoutEffect, useEffect, useRef } from "react";
22
import styled, { ThemeProvider } from "styled-components";
33
import useTheme from "../useTheme";
44
import useTranslatedLabels from "../useTranslatedLabels";
@@ -20,11 +20,12 @@ const Listbox = ({
2020
optionalItem,
2121
searchable,
2222
handleOptionOnClick,
23-
styles,
23+
getSelectWidth,
2424
}: ListboxProps): JSX.Element => {
2525
const colorsTheme = useTheme();
2626
const translatedLabels = useTranslatedLabels();
2727
const listboxRef = useRef(null);
28+
const [styles, setStyles] = useState(null);
2829

2930
let globalIndex = optional && !multiple ? 0 : -1; // index for options, starting from 0 to options.length -1
3031
const mapOptionFunc = (option, mapIndex) => {
@@ -89,6 +90,21 @@ const Listbox = ({
8990
visualFocusedOptionEl?.scrollIntoView?.({ block: "nearest", inline: "start" });
9091
}, [visualFocusIndex]);
9192

93+
const handleResize = () => {
94+
setStyles({ width: getSelectWidth() });
95+
};
96+
97+
useLayoutEffect(() => {
98+
handleResize();
99+
} , [getSelectWidth]);
100+
101+
useEffect(() => {
102+
window.addEventListener("resize", handleResize);
103+
return () => {
104+
window.removeEventListener("resize", handleResize);
105+
};
106+
}, [getSelectWidth]);
107+
92108
return (
93109
<ThemeProvider theme={colorsTheme.select}>
94110
<ListboxContainer

lib/src/select/Select.stories.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ const SelectListbox = () => (
379379
optionalItem={{ label: "Empty", value: "" }}
380380
searchable={false}
381381
handleOptionOnClick={() => {}}
382-
styles={{ width: 360 }}
382+
getSelectWidth={() => 360}
383383
/>
384384
</ExampleContainer>
385385
<ExampleContainer pseudoState="pseudo-active">
@@ -395,7 +395,8 @@ const SelectListbox = () => (
395395
optionalItem={{ label: "Empty", value: "" }}
396396
searchable={false}
397397
handleOptionOnClick={() => {}}
398-
styles={{ width: 360 }}
398+
getSelectWidth={() => 360}
399+
399400
/>
400401
</ExampleContainer>
401402
<ExampleContainer>
@@ -411,7 +412,7 @@ const SelectListbox = () => (
411412
optionalItem={{ label: "Empty", value: "" }}
412413
searchable={false}
413414
handleOptionOnClick={() => {}}
414-
styles={{ width: 360 }}
415+
getSelectWidth={() => 360}
415416
/>
416417
</ExampleContainer>
417418
<ExampleContainer pseudoState="pseudo-hover">
@@ -427,7 +428,7 @@ const SelectListbox = () => (
427428
optionalItem={{ label: "Empty", value: "" }}
428429
searchable={false}
429430
handleOptionOnClick={() => {}}
430-
styles={{ width: 360 }}
431+
getSelectWidth={() => 360}
431432
/>
432433
</ExampleContainer>
433434
<ExampleContainer pseudoState="pseudo-active">
@@ -443,7 +444,7 @@ const SelectListbox = () => (
443444
optionalItem={{ label: "Empty", value: "" }}
444445
searchable={false}
445446
handleOptionOnClick={() => {}}
446-
styles={{ width: 360 }}
447+
getSelectWidth={() => 360}
447448
/>
448449
</ExampleContainer>
449450
</>

lib/src/select/Select.test.js

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -199,32 +199,17 @@ const grouped_icon_options = [
199199
];
200200

201201
describe("Select component tests", () => {
202-
test("Renders with correct label", () => {
202+
test("When clicking the label, the focus goes to the select", () => {
203203
const { getByText, getByRole } = render(
204204
<DxcSelect label="test-select-label" helperText="test-select-helper-text" placeholder="Example text" />
205205
);
206206
const select = getByRole("combobox");
207207
const label = getByText("test-select-label");
208208

209-
expect(label).toBeTruthy();
210209
userEvent.click(label);
211210
expect(document.activeElement).toEqual(select);
212211
});
213-
test("Renders with correct helper text and placeholder", () => {
214-
const { getByText } = render(
215-
<DxcSelect label="test-select-label" helperText="test-select-helper-text" placeholder="Example text" />
216-
);
217-
218-
expect(getByText("test-select-helper-text")).toBeTruthy();
219-
expect(getByText("Example text")).toBeTruthy();
220-
});
221-
test("Renders with correct optional label", () => {
222-
const { getByText } = render(<DxcSelect label="test-select-label" optional />);
223-
224-
expect(getByText("test-select-label")).toBeTruthy();
225-
expect(getByText("(Optional)")).toBeTruthy();
226-
});
227-
test("Renders with error message and correct aria attributes", () => {
212+
test("Renders with correct aria attributes when is in error state", () => {
228213
const { getByText, getByRole } = render(<DxcSelect label="Error label" error="Error message." />);
229214
const select = getByRole("combobox");
230215
const errorMessage = getByText("Error message.");
@@ -293,26 +278,45 @@ describe("Select component tests", () => {
293278
expect(getByText("Option 02, Option 03, Option 04, Option 06")).toBeTruthy();
294279
expect(submitInput.value).toBe("4,2,6,3");
295280
});
296-
test("Disabled select - Clear all options action must be shown but not clickable", () => {
297-
const { getByRole, getByText, queryByRole } = render(
298-
<DxcSelect label="test-select-label" value={["1", "2"]} options={single_options} disabled searchable multiple />
281+
test("Disabled select - Cannot gain focus or open the listbox via click", () => {
282+
const { getByRole, queryByRole } = render(
283+
<DxcSelect label="test-select-label" value={["1", "2"]} options={single_options} disabled />
299284
);
300285
const select = getByRole("combobox");
301286

302287
expect(select.getAttribute("aria-disabled")).toBe("true");
303288
userEvent.click(select);
304289
expect(queryByRole("listbox")).toBeFalsy();
290+
expect(document.activeElement === select).toBeFalsy();
291+
});
292+
test("Disabled select - Clear all options action must be shown but not clickable", () => {
293+
const { getByRole, getByText } = render(
294+
<DxcSelect label="test-select-label" value={["1", "2"]} options={single_options} disabled searchable multiple />
295+
);
296+
305297
userEvent.click(getByRole("button"));
306298
expect(getByText("Option 01, Option 02")).toBeTruthy();
307299
});
308-
test("Focused select does not open the listbox", () => {
300+
test("Disabled select - Does not call onBlur event", () => {
301+
const onBlur = jest.fn();
302+
const { getByRole } = render(
303+
<DxcSelect label="test-select-label" options={single_options} disabled onBlur={onBlur} />
304+
);
305+
const select = getByRole("combobox");
306+
307+
userEvent.click(select);
308+
fireEvent.keyDown(getByRole("combobox"), { key: "Tab", code: "Tab", keyCode: 9, charCode: 9 });
309+
expect(onBlur).not.toHaveBeenCalled();
310+
});
311+
test("Disabled select - When the component gains the focus, the listbox does not open", () => {
309312
const { getByRole, queryByRole } = render(
310313
<DxcSelect label="test-select-label" value={["1", "2"]} options={single_options} disabled searchable multiple />
311314
);
312315
const select = getByRole("combobox");
313316

314317
fireEvent.focus(select);
315318
expect(queryByRole("listbox")).toBeFalsy();
319+
expect(document.activeElement === select).toBeFalsy();
316320
});
317321
test("Controlled - Single selection - Not optional constraint", () => {
318322
const onChange = jest.fn();

lib/src/select/Select.tsx

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-nocheck
2-
import React, { useMemo, useRef, useState, useCallback, useEffect } from "react";
2+
import React, { useMemo, useRef, useState, useCallback } from "react";
33
import styled, { ThemeProvider } from "styled-components";
44
import useTheme from "../useTheme";
55
import useTranslatedLabels from "../useTranslatedLabels";
@@ -129,7 +129,6 @@ const DxcSelect = React.forwardRef<RefType, SelectPropsType>(
129129
const [searchValue, setSearchValue] = useState("");
130130
const [visualFocusIndex, changeVisualFocusIndex] = useState(-1);
131131
const [isOpen, changeIsOpen] = useState(false);
132-
const [listboxStyles, setListboxStyles] = useState(null);
133132

134133
const selectRef = useRef(null);
135134
const selectSearchInputRef = useRef(null);
@@ -195,8 +194,9 @@ const DxcSelect = React.forwardRef<RefType, SelectPropsType>(
195194
}
196195
};
197196
const handleSelectOnKeyDown = (event) => {
198-
switch (event.keyCode) {
199-
case 40: // Arrow Down
197+
switch (event.key) {
198+
case "Down":
199+
case "ArrowDown":
200200
event.preventDefault();
201201
singleSelectionIndex !== undefined &&
202202
(!isOpen || (visualFocusIndex === -1 && singleSelectionIndex > -1 && singleSelectionIndex <= lastOptionIndex))
@@ -207,7 +207,8 @@ const DxcSelect = React.forwardRef<RefType, SelectPropsType>(
207207
});
208208
openOptions();
209209
break;
210-
case 38: // Arrow Up
210+
case "Up":
211+
case "ArrowUp":
211212
event.preventDefault();
212213
singleSelectionIndex !== undefined &&
213214
(!isOpen || (visualFocusIndex === -1 && singleSelectionIndex > -1 && singleSelectionIndex <= lastOptionIndex))
@@ -217,12 +218,13 @@ const DxcSelect = React.forwardRef<RefType, SelectPropsType>(
217218
);
218219
openOptions();
219220
break;
220-
case 27: // Esc
221+
case "Esc":
222+
case "Escape":
221223
event.preventDefault();
222224
closeOptions();
223225
setSearchValue("");
224226
break;
225-
case 13: // Enter
227+
case "Enter":
226228
if (isOpen && visualFocusIndex >= 0) {
227229
let accLength = optional && !multiple ? 1 : 0;
228230
if (searchable) {
@@ -289,17 +291,10 @@ const DxcSelect = React.forwardRef<RefType, SelectPropsType>(
289291
[handleSelectChangeValue, closeOptions, multiple]
290292
);
291293

292-
const handleListboxResize = () => {
294+
const getSelectWidth = useCallback(() => {
293295
const rect = selectRef?.current?.getBoundingClientRect();
294-
setListboxStyles({ width: rect?.width });
295-
};
296-
useEffect(() => {
297-
handleListboxResize();
298-
window.addEventListener("resize", handleListboxResize);
299-
return () => {
300-
window.removeEventListener("resize", handleListboxResize);
301-
};
302-
}, [setListboxStyles]);
296+
return rect?.width;
297+
}, []);
303298

304299
return (
305300
<ThemeProvider theme={colorsTheme.select}>
@@ -328,15 +323,15 @@ const DxcSelect = React.forwardRef<RefType, SelectPropsType>(
328323
onFocus={handleSelectOnFocus}
329324
onKeyDown={handleSelectOnKeyDown}
330325
ref={selectRef}
331-
tabIndex={tabIndex}
326+
tabIndex={disabled ? -1 : tabIndex}
332327
role="combobox"
333328
aria-controls={optionsListId}
334329
aria-disabled={disabled}
335330
aria-expanded={isOpen}
336331
aria-haspopup="listbox"
337332
aria-labelledby={label ? selectLabelId : undefined}
338333
aria-activedescendant={visualFocusIndex >= 0 ? `option-${visualFocusIndex}` : undefined}
339-
aria-invalid={error ? "true" : "false"}
334+
aria-invalid={error ? true : false}
340335
aria-errormessage={error ? errorId : undefined}
341336
aria-required={!disabled && !optional}
342337
>
@@ -438,7 +433,7 @@ const DxcSelect = React.forwardRef<RefType, SelectPropsType>(
438433
optionalItem={optionalItem}
439434
searchable={searchable}
440435
handleOptionOnClick={handleOptionOnClick}
441-
styles={listboxStyles}
436+
getSelectWidth={getSelectWidth}
442437
/>
443438
</Popover.Content>
444439
</Popover.Root>

lib/src/select/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export type ListboxProps = {
193193
optionalItem: Option;
194194
searchable: boolean;
195195
handleOptionOnClick: (option: Option) => void;
196-
styles: { width: number };
196+
getSelectWidth: () => number;
197197
};
198198

199199
/**

0 commit comments

Comments
 (0)