@khanacademy/wonder-blocks-dropdown 2.6.8 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/es/index.js +172 -656
- package/dist/index.js +228 -326
- package/package.json +11 -10
- package/src/components/__tests__/dropdown-core.test.js +7 -3
- package/src/components/__tests__/search-text-input.test.js +124 -57
- package/src/components/__tests__/single-select.test.js +102 -1
- package/src/components/dropdown-core-virtualized.js +11 -34
- package/src/components/dropdown-core.js +17 -1
- package/src/components/search-text-input.js +15 -149
- package/src/components/single-select.stories.js +1 -0
- package/src/util/__tests__/dropdown-menu-styles.test.js +100 -0
- package/src/util/constants.js +7 -1
- package/src/util/dropdown-menu-styles.js +65 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-dropdown",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"description": "Dropdown variants for Wonder Blocks.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,17 +16,18 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/runtime": "^7.16.3",
|
|
19
|
-
"@khanacademy/wonder-blocks-button": "^2.11.
|
|
20
|
-
"@khanacademy/wonder-blocks-clickable": "^2.2.
|
|
19
|
+
"@khanacademy/wonder-blocks-button": "^2.11.5",
|
|
20
|
+
"@khanacademy/wonder-blocks-clickable": "^2.2.6",
|
|
21
21
|
"@khanacademy/wonder-blocks-color": "^1.1.20",
|
|
22
|
-
"@khanacademy/wonder-blocks-core": "^4.3.
|
|
23
|
-
"@khanacademy/wonder-blocks-icon": "^1.2.
|
|
24
|
-
"@khanacademy/wonder-blocks-icon-button": "^3.4.
|
|
25
|
-
"@khanacademy/wonder-blocks-layout": "^1.4.
|
|
26
|
-
"@khanacademy/wonder-blocks-modal": "^2.3.
|
|
22
|
+
"@khanacademy/wonder-blocks-core": "^4.3.1",
|
|
23
|
+
"@khanacademy/wonder-blocks-icon": "^1.2.27",
|
|
24
|
+
"@khanacademy/wonder-blocks-icon-button": "^3.4.6",
|
|
25
|
+
"@khanacademy/wonder-blocks-layout": "^1.4.9",
|
|
26
|
+
"@khanacademy/wonder-blocks-modal": "^2.3.1",
|
|
27
|
+
"@khanacademy/wonder-blocks-search-field": "^1.0.4",
|
|
27
28
|
"@khanacademy/wonder-blocks-spacing": "^3.0.5",
|
|
28
29
|
"@khanacademy/wonder-blocks-timing": "^2.1.0",
|
|
29
|
-
"@khanacademy/wonder-blocks-typography": "^1.1.
|
|
30
|
+
"@khanacademy/wonder-blocks-typography": "^1.1.31"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"@popperjs/core": "^2.10.1",
|
|
@@ -39,6 +40,6 @@
|
|
|
39
40
|
"react-window": "^1.8.5"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"wb-dev-build-settings": "^0.
|
|
43
|
+
"wb-dev-build-settings": "^0.4.0"
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {fireEvent, render, screen} from "@testing-library/react";
|
|
3
|
+
import {fireEvent, render, screen, waitFor} from "@testing-library/react";
|
|
4
4
|
import userEvent from "@testing-library/user-event";
|
|
5
5
|
|
|
6
6
|
import OptionItem from "../option-item.js";
|
|
@@ -784,7 +784,9 @@ describe("DropdownCore", () => {
|
|
|
784
784
|
const searchField = await screen.findByPlaceholderText("Filter");
|
|
785
785
|
|
|
786
786
|
// Assert
|
|
787
|
-
|
|
787
|
+
waitFor(() => {
|
|
788
|
+
expect(searchField).toHaveFocus();
|
|
789
|
+
});
|
|
788
790
|
});
|
|
789
791
|
|
|
790
792
|
it("should focus on the item after clicking on it", async () => {
|
|
@@ -826,7 +828,9 @@ describe("DropdownCore", () => {
|
|
|
826
828
|
userEvent.click(item);
|
|
827
829
|
|
|
828
830
|
// Assert
|
|
829
|
-
|
|
831
|
+
waitFor(() => {
|
|
832
|
+
expect(item).toHaveFocus();
|
|
833
|
+
});
|
|
830
834
|
});
|
|
831
835
|
});
|
|
832
836
|
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
import "
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
5
|
|
|
6
6
|
import SearchTextInput from "../search-text-input.js";
|
|
7
7
|
|
|
8
8
|
describe("SearchTextInput", () => {
|
|
9
|
-
|
|
9
|
+
test("text input container should be focused when focusing on the input", () => {
|
|
10
10
|
// Arrange
|
|
11
|
-
|
|
11
|
+
render(
|
|
12
12
|
<SearchTextInput
|
|
13
13
|
searchText=""
|
|
14
14
|
testId="search-text-input"
|
|
@@ -16,18 +16,18 @@ describe("SearchTextInput", () => {
|
|
|
16
16
|
/>,
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
-
const input =
|
|
19
|
+
const input = screen.getByTestId("search-text-input");
|
|
20
20
|
|
|
21
21
|
// Act
|
|
22
|
-
input.
|
|
22
|
+
input.focus();
|
|
23
23
|
|
|
24
24
|
// Assert
|
|
25
|
-
expect(
|
|
25
|
+
expect(input).toHaveFocus();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
test("text input should not be focused when losing focus", () => {
|
|
29
29
|
// Arrange
|
|
30
|
-
|
|
30
|
+
render(
|
|
31
31
|
<SearchTextInput
|
|
32
32
|
searchText=""
|
|
33
33
|
testId="search-text-input"
|
|
@@ -35,23 +35,23 @@ describe("SearchTextInput", () => {
|
|
|
35
35
|
/>,
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
-
const input =
|
|
38
|
+
const input = screen.getByTestId("search-text-input");
|
|
39
39
|
// focus in
|
|
40
|
-
input.
|
|
40
|
+
input.focus();
|
|
41
41
|
|
|
42
42
|
// Act
|
|
43
43
|
// focus out
|
|
44
|
-
input.
|
|
44
|
+
input.blur();
|
|
45
45
|
|
|
46
46
|
// Assert
|
|
47
|
-
expect(
|
|
47
|
+
expect(input).not.toHaveFocus();
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
test("onChange should be invoked if text input changes", () => {
|
|
51
51
|
// Arrange
|
|
52
52
|
const onChangeMock = jest.fn();
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
render(
|
|
55
55
|
<SearchTextInput
|
|
56
56
|
searchText=""
|
|
57
57
|
testId="search-text-input"
|
|
@@ -59,87 +59,154 @@ describe("SearchTextInput", () => {
|
|
|
59
59
|
/>,
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
-
const input =
|
|
62
|
+
const input = screen.getByTestId("search-text-input");
|
|
63
63
|
|
|
64
64
|
// Act
|
|
65
|
-
|
|
66
|
-
target: {value: "query"},
|
|
67
|
-
preventDefault: jest.fn(),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
wrapper.update();
|
|
65
|
+
userEvent.paste(input, "value");
|
|
71
66
|
|
|
72
67
|
// Assert
|
|
73
|
-
expect(onChangeMock).toHaveBeenCalledWith("
|
|
68
|
+
expect(onChangeMock).toHaveBeenCalledWith("value");
|
|
74
69
|
});
|
|
75
70
|
|
|
76
|
-
|
|
71
|
+
test("displays the dismiss button when search text exists", () => {
|
|
77
72
|
// Arrange
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
const SearchFieldWrapper = () => {
|
|
74
|
+
const [value, setValue] = React.useState("");
|
|
75
|
+
return (
|
|
76
|
+
<SearchTextInput
|
|
77
|
+
searchText={value}
|
|
78
|
+
testId="search-text-input"
|
|
79
|
+
onChange={setValue}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
render(<SearchFieldWrapper />);
|
|
85
|
+
|
|
86
|
+
const input = screen.getByTestId("search-text-input");
|
|
87
|
+
|
|
88
|
+
// Act
|
|
89
|
+
userEvent.paste(input, "value");
|
|
90
|
+
|
|
91
|
+
// Assert
|
|
92
|
+
const clearIconButton = screen.queryByRole("button");
|
|
93
|
+
expect(clearIconButton).toBeInTheDocument();
|
|
94
|
+
});
|
|
86
95
|
|
|
87
|
-
|
|
96
|
+
test("search should be cleared if the clear icon is clicked", () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const SearchFieldWrapper = () => {
|
|
99
|
+
const [value, setValue] = React.useState("initial value");
|
|
100
|
+
return (
|
|
101
|
+
<SearchTextInput
|
|
102
|
+
searchText={value}
|
|
103
|
+
testId="search-text-input"
|
|
104
|
+
onChange={setValue}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
render(<SearchFieldWrapper />);
|
|
110
|
+
|
|
111
|
+
const input = screen.getByTestId("search-text-input");
|
|
112
|
+
const clearIconButton = screen.queryByRole("button");
|
|
88
113
|
|
|
89
114
|
// Act
|
|
90
|
-
|
|
91
|
-
target: {value: "query"},
|
|
92
|
-
preventDefault: jest.fn(),
|
|
93
|
-
});
|
|
115
|
+
userEvent.click(clearIconButton);
|
|
94
116
|
|
|
95
117
|
// Assert
|
|
96
|
-
expect(
|
|
118
|
+
expect(input).toHaveValue("");
|
|
97
119
|
});
|
|
98
120
|
|
|
99
|
-
|
|
121
|
+
test("focus should return to the input element after clear button is clicked", () => {
|
|
100
122
|
// Arrange
|
|
101
|
-
const
|
|
123
|
+
const SearchFieldWrapper = () => {
|
|
124
|
+
const [value, setValue] = React.useState("");
|
|
125
|
+
return (
|
|
126
|
+
<SearchTextInput
|
|
127
|
+
searchText={value}
|
|
128
|
+
testId="search-text-input"
|
|
129
|
+
onChange={setValue}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
render(<SearchFieldWrapper />);
|
|
135
|
+
|
|
136
|
+
const input = screen.getByTestId("search-text-input");
|
|
137
|
+
userEvent.paste(input, "something");
|
|
102
138
|
|
|
103
|
-
|
|
139
|
+
// Act
|
|
140
|
+
const clearIconButton = screen.queryByRole("button");
|
|
141
|
+
userEvent.click(clearIconButton);
|
|
142
|
+
|
|
143
|
+
// Assert
|
|
144
|
+
expect(input).toHaveFocus();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("placeholder should be updated by the parent component", () => {
|
|
148
|
+
// Arrange
|
|
149
|
+
const {rerender} = render(
|
|
104
150
|
<SearchTextInput
|
|
105
151
|
searchText="query"
|
|
106
|
-
onChange={() =>
|
|
107
|
-
|
|
152
|
+
onChange={() => {}}
|
|
153
|
+
labels={{
|
|
154
|
+
clearSearch: "Clear",
|
|
155
|
+
filter: "Filter",
|
|
156
|
+
}}
|
|
157
|
+
testId="search-text-input"
|
|
108
158
|
/>,
|
|
109
159
|
);
|
|
110
160
|
|
|
111
|
-
const
|
|
161
|
+
const input = screen.getByTestId("search-text-input");
|
|
112
162
|
|
|
113
163
|
// Act
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
164
|
+
rerender(
|
|
165
|
+
<SearchTextInput
|
|
166
|
+
searchText="query"
|
|
167
|
+
onChange={() => {}}
|
|
168
|
+
labels={{
|
|
169
|
+
clearSearch: "Dismiss",
|
|
170
|
+
filter: "Search",
|
|
171
|
+
}}
|
|
172
|
+
testId="search-text-input"
|
|
173
|
+
/>,
|
|
174
|
+
);
|
|
117
175
|
|
|
118
176
|
// Assert
|
|
119
|
-
expect(
|
|
177
|
+
expect(input).toHaveAttribute("placeholder", "Search");
|
|
120
178
|
});
|
|
121
179
|
|
|
122
|
-
|
|
180
|
+
test("button label should be updated by the parent component", () => {
|
|
123
181
|
// Arrange
|
|
124
|
-
const
|
|
182
|
+
const {rerender} = render(
|
|
125
183
|
<SearchTextInput
|
|
126
184
|
searchText="query"
|
|
127
|
-
onChange={() =>
|
|
185
|
+
onChange={() => {}}
|
|
128
186
|
labels={{
|
|
129
187
|
clearSearch: "Clear",
|
|
130
188
|
filter: "Filter",
|
|
131
189
|
}}
|
|
190
|
+
testId="search-text-input"
|
|
132
191
|
/>,
|
|
133
192
|
);
|
|
134
193
|
|
|
135
|
-
|
|
136
|
-
wrapper.setProps({labels: {clearSearch: "Dismiss", filter: "Search"}});
|
|
194
|
+
const clearIconButton = screen.queryByRole("button");
|
|
137
195
|
|
|
138
|
-
|
|
196
|
+
// Act
|
|
197
|
+
rerender(
|
|
198
|
+
<SearchTextInput
|
|
199
|
+
searchText="query"
|
|
200
|
+
onChange={() => {}}
|
|
201
|
+
labels={{
|
|
202
|
+
clearSearch: "Dismiss",
|
|
203
|
+
filter: "Search",
|
|
204
|
+
}}
|
|
205
|
+
testId="search-text-input"
|
|
206
|
+
/>,
|
|
207
|
+
);
|
|
139
208
|
|
|
140
209
|
// Assert
|
|
141
|
-
expect(
|
|
142
|
-
labels: {clearSearch: "Dismiss", filter: "Search"},
|
|
143
|
-
});
|
|
210
|
+
expect(clearIconButton).toHaveAttribute("aria-label", "Dismiss");
|
|
144
211
|
});
|
|
145
212
|
});
|
|
@@ -428,7 +428,6 @@ describe("SingleSelect", () => {
|
|
|
428
428
|
// open dropdown
|
|
429
429
|
userEvent.click(opener);
|
|
430
430
|
userEvent.click(screen.getByText("Toggle B"));
|
|
431
|
-
jest.advanceTimersByTime(100);
|
|
432
431
|
|
|
433
432
|
// Assert
|
|
434
433
|
// NOTE: the opener text is only updated in response to changes to the
|
|
@@ -591,4 +590,106 @@ describe("SingleSelect", () => {
|
|
|
591
590
|
expect(dismissBtn).toHaveFocus();
|
|
592
591
|
});
|
|
593
592
|
});
|
|
593
|
+
|
|
594
|
+
describe("Custom listbox styles", () => {
|
|
595
|
+
it("should apply the default maxHeight to the listbox", () => {
|
|
596
|
+
// Arrange
|
|
597
|
+
|
|
598
|
+
// Act
|
|
599
|
+
render(
|
|
600
|
+
<SingleSelect
|
|
601
|
+
onChange={onChange}
|
|
602
|
+
opened={true}
|
|
603
|
+
placeholder="Choose"
|
|
604
|
+
selectedValue="2"
|
|
605
|
+
>
|
|
606
|
+
<OptionItem label="item 1" value="1" />
|
|
607
|
+
<OptionItem label="item 2" value="2" />
|
|
608
|
+
<OptionItem label="item 3" value="3" />
|
|
609
|
+
</SingleSelect>,
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Assert
|
|
613
|
+
const dropdownMenu = screen.getByRole("listbox");
|
|
614
|
+
expect(dropdownMenu).toHaveStyle("max-height: 132px");
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("should apply the default maxHeight to a filterable listbox", () => {
|
|
618
|
+
// Arrange
|
|
619
|
+
|
|
620
|
+
// Act
|
|
621
|
+
render(
|
|
622
|
+
<SingleSelect
|
|
623
|
+
onChange={onChange}
|
|
624
|
+
opened={true}
|
|
625
|
+
isFilterable={true}
|
|
626
|
+
placeholder="Choose"
|
|
627
|
+
selectedValue="2"
|
|
628
|
+
>
|
|
629
|
+
<OptionItem label="item 1" value="1" />
|
|
630
|
+
<OptionItem label="item 2" value="2" />
|
|
631
|
+
<OptionItem label="item 3" value="3" />
|
|
632
|
+
</SingleSelect>,
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
// Assert
|
|
636
|
+
const dropdownMenu = screen.getByRole("listbox");
|
|
637
|
+
expect(dropdownMenu).toHaveStyle("max-height: 184px");
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("should apply the default maxHeight to a virtualized listbox", () => {
|
|
641
|
+
// Arrange
|
|
642
|
+
const optionItems = new Array(1000)
|
|
643
|
+
.fill(null)
|
|
644
|
+
.map((_, i) => (
|
|
645
|
+
<OptionItem
|
|
646
|
+
key={i}
|
|
647
|
+
value={(i + 1).toString()}
|
|
648
|
+
label={`item ${i + 1}`}
|
|
649
|
+
/>
|
|
650
|
+
));
|
|
651
|
+
|
|
652
|
+
// Act
|
|
653
|
+
render(
|
|
654
|
+
<SingleSelect
|
|
655
|
+
onChange={onChange}
|
|
656
|
+
opened={true}
|
|
657
|
+
isFilterable={true}
|
|
658
|
+
placeholder="Choose"
|
|
659
|
+
selectedValue="2"
|
|
660
|
+
>
|
|
661
|
+
{optionItems}
|
|
662
|
+
</SingleSelect>,
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
// Assert
|
|
666
|
+
const dropdownMenu = screen.getByRole("listbox");
|
|
667
|
+
// Max allowed height
|
|
668
|
+
expect(dropdownMenu).toHaveStyle("max-height: 384px");
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it("should override the default maxHeight to the listbox if a custom dropdownStyle is set", () => {
|
|
672
|
+
// Arrange
|
|
673
|
+
const customMaxHeight = 200;
|
|
674
|
+
|
|
675
|
+
// Act
|
|
676
|
+
render(
|
|
677
|
+
<SingleSelect
|
|
678
|
+
onChange={onChange}
|
|
679
|
+
opened={true}
|
|
680
|
+
placeholder="Choose"
|
|
681
|
+
selectedValue="2"
|
|
682
|
+
dropdownStyle={{maxHeight: customMaxHeight}}
|
|
683
|
+
>
|
|
684
|
+
<OptionItem label="item 1" value="1" />
|
|
685
|
+
<OptionItem label="item 2" value="2" />
|
|
686
|
+
<OptionItem label="item 3" value="3" />
|
|
687
|
+
</SingleSelect>,
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
// Assert
|
|
691
|
+
const dropdownMenu = screen.getByRole("listbox");
|
|
692
|
+
expect(dropdownMenu).toHaveStyle("max-height: 200px");
|
|
693
|
+
});
|
|
694
|
+
});
|
|
594
695
|
});
|
|
@@ -16,9 +16,11 @@ import type {DropdownItem} from "../util/types.js";
|
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
18
|
DROPDOWN_ITEM_HEIGHT,
|
|
19
|
+
MAX_VISIBLE_ITEMS,
|
|
19
20
|
SEARCH_ITEM_HEIGHT,
|
|
20
21
|
SEPARATOR_ITEM_HEIGHT,
|
|
21
22
|
} from "../util/constants.js";
|
|
23
|
+
import {getDropdownMenuHeight} from "../util/dropdown-menu-styles.js";
|
|
22
24
|
|
|
23
25
|
type Props = {|
|
|
24
26
|
/**
|
|
@@ -56,23 +58,19 @@ type State = {|
|
|
|
56
58
|
height: ?number,
|
|
57
59
|
|};
|
|
58
60
|
|
|
59
|
-
/**
|
|
60
|
-
* Maximum visible items inside the dropdown list.
|
|
61
|
-
* Based on the defined height that we're using, this is the maximium
|
|
62
|
-
* number of items that can fit into the visible porition of the
|
|
63
|
-
* dropdowns list box.
|
|
64
|
-
*/
|
|
65
|
-
const MAX_VISIBLE_ITEMS = 9;
|
|
66
|
-
|
|
67
61
|
/**
|
|
68
62
|
* A react-window's List wrapper that instantiates the virtualized list and
|
|
69
63
|
* dynamically calculates the item height depending on the type
|
|
70
64
|
*/
|
|
71
65
|
class DropdownCoreVirtualized extends React.Component<Props, State> {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
constructor(props: Props) {
|
|
67
|
+
super(props);
|
|
68
|
+
|
|
69
|
+
this.state = {
|
|
70
|
+
height: getDropdownMenuHeight(props.data),
|
|
71
|
+
width: props.width,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
76
74
|
|
|
77
75
|
componentDidMount() {
|
|
78
76
|
const {schedule} = this.props;
|
|
@@ -122,31 +120,10 @@ class DropdownCoreVirtualized extends React.Component<Props, State> {
|
|
|
122
120
|
*/
|
|
123
121
|
setHeight() {
|
|
124
122
|
// calculate dropdown's height depending on the type of items
|
|
125
|
-
const height = this.
|
|
123
|
+
const height = getDropdownMenuHeight(this.props.data);
|
|
126
124
|
this.setState({height});
|
|
127
125
|
}
|
|
128
126
|
|
|
129
|
-
/**
|
|
130
|
-
* The list height that is automatically calculated depending on the
|
|
131
|
-
* component's type of each item (e.g. Separator, Option, Search, etc)
|
|
132
|
-
*/
|
|
133
|
-
getHeight(): number {
|
|
134
|
-
// calculate using the first 10 items on the array as we want to display
|
|
135
|
-
// this number of elements in the visible area
|
|
136
|
-
return this.props.data
|
|
137
|
-
.slice(0, MAX_VISIBLE_ITEMS)
|
|
138
|
-
.reduce((sum, item) => {
|
|
139
|
-
if (SeparatorItem.isClassOf(item.component)) {
|
|
140
|
-
return sum + SEPARATOR_ITEM_HEIGHT;
|
|
141
|
-
} else if (SearchTextInput.isClassOf(item.component)) {
|
|
142
|
-
// search text input height
|
|
143
|
-
return sum + SEARCH_ITEM_HEIGHT;
|
|
144
|
-
} else {
|
|
145
|
-
return sum + DROPDOWN_ITEM_HEIGHT;
|
|
146
|
-
}
|
|
147
|
-
}, 0);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
127
|
/**
|
|
151
128
|
* Calculates item height
|
|
152
129
|
*/
|
|
@@ -25,6 +25,10 @@ import SearchTextInput from "./search-text-input.js";
|
|
|
25
25
|
import {defaultLabels, keyCodes, searchInputStyle} from "../util/constants.js";
|
|
26
26
|
import type {DropdownItem} from "../util/types.js";
|
|
27
27
|
import DropdownPopper from "./dropdown-popper.js";
|
|
28
|
+
import {
|
|
29
|
+
generateDropdownMenuStyles,
|
|
30
|
+
getDropdownMenuHeight,
|
|
31
|
+
} from "../util/dropdown-menu-styles.js";
|
|
28
32
|
|
|
29
33
|
/**
|
|
30
34
|
* The number of options to apply the virtualized list to.
|
|
@@ -762,6 +766,14 @@ class DropdownCore extends React.Component<Props, State> {
|
|
|
762
766
|
? openerStyle.getPropertyValue("width")
|
|
763
767
|
: 0;
|
|
764
768
|
|
|
769
|
+
// Vertical padding of the dropdown menu + borders
|
|
770
|
+
const initialHeight = 12;
|
|
771
|
+
|
|
772
|
+
const maxDropdownHeight = getDropdownMenuHeight(
|
|
773
|
+
this.props.items,
|
|
774
|
+
initialHeight,
|
|
775
|
+
);
|
|
776
|
+
|
|
765
777
|
return (
|
|
766
778
|
<View
|
|
767
779
|
// Stop propagation to prevent the mouseup listener on the
|
|
@@ -772,7 +784,11 @@ class DropdownCore extends React.Component<Props, State> {
|
|
|
772
784
|
styles.dropdown,
|
|
773
785
|
light && styles.light,
|
|
774
786
|
isReferenceHidden && styles.hidden,
|
|
775
|
-
|
|
787
|
+
generateDropdownMenuStyles(
|
|
788
|
+
minDropdownWidth,
|
|
789
|
+
maxDropdownHeight,
|
|
790
|
+
),
|
|
791
|
+
|
|
776
792
|
dropdownStyle,
|
|
777
793
|
]}
|
|
778
794
|
>
|