big-design
big-design copied to clipboard
feat(component): create base combobox functionality
trafficstars
What?
Created a ComboBox component to add diverse usage than our current select component
Why?
There are other functionalities such as being able to page the results in a selection or adding the ability to first search through a set to then provide the combobox options that shouldn't be added to the current select component.
Screenshots/Screen Recordings
Testing/Proof
dev.tsx code
import { ComboBox, Form, FormGroup, Box } from '@bigcommerce/big-design';
import React, { FunctionComponent, useState } from 'react';
const allCountries = [
"Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria",
"Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan",
"Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cabo Verde", "Cambodia",
"Cameroon", "Canada", "Central African Republic", "Chad", "Chile", "China", "Colombia", "Comoros", "Congo (Congo-Brazzaville)", "Costa Rica",
"Croatia", "Cuba", "Cyprus", "Czechia (Czech Republic)", "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador",
"Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Eswatini (fmr. " + "Swaziland)", "Ethiopia", "Fiji", "Finland", "France",
"Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau",
"Guyana", "Haiti", "Holy See", "Honduras", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq",
"Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait",
"Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico",
"Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar (formerly Burma)", "Namibia", "Nauru",
"Nepal", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "North Korea", "North Macedonia", "Norway", "Oman",
"Pakistan", "Palau", "Palestine State", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal",
"Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe",
"Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia",
"South Africa", "South Korea", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Sweden", "Switzerland", "Syria",
"Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
"Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States of America", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
"Vietnam", "Yemen", "Zambia", "Zimbabwe"
];
const ComboBoxTest: FunctionComponent = () => {
// First ComboBox (paged)
const [value, setValue] = useState('mx');
const handleChange = (val) => setValue(val);
const [optionsLoading, setOptionsLoading] = useState(false);
type Option = { value: string; content: string };
const generateOptions = (start: number, num: number): Option[] => {
return allCountries.slice(start, start + num).map((country) => ({
value: country.toLowerCase().replace(/[^a-z]/g, '-'),
content: country,
}));
};
const [selectOptions, setSelectOptions] = useState<Option[]>(generateOptions(0, 9));
const expandOptions = async () => {
setOptionsLoading(true);
// Simulate async option fetch
await new Promise((resolve) => setTimeout(resolve, 1000));
setSelectOptions((prevOptions) => {
const start = prevOptions.length;
const newOptions = generateOptions(start, 9);
return [...prevOptions, ...newOptions];
});
setOptionsLoading(false);
};
const hasMoreOptions = selectOptions.length < allCountries.length;
// Second ComboBox (search after 3 letters)
const [searchValue, setSearchValue] = useState('');
const [searchOptions, setSearchOptions] = useState<Option[]>([]);
const [searchLoading, setSearchLoading] = useState(false);
const handleSearchInputChange = async (input: string) => {
setSearchValue(input);
if (input.length < 2) {
setSearchOptions([]);
return;
}
setSearchLoading(true);
// Simulate async fetch
await new Promise((resolve) => setTimeout(resolve, 500));
const filtered = allCountries
.filter((country) => country.toLowerCase().includes(input.toLowerCase()))
.map((country) => ({
value: country.toLowerCase().replace(/[^a-z]/g, '-'),
content: country,
}));
setSearchOptions(filtered);
setSearchLoading(false);
};
const handleSearchOptionChange = (val) => setSearchValue(val);
return (
<Box padding={'xxLarge'}>
<Form>
<FormGroup>
<ComboBox
filterable={true}
label="Countries (Paged)"
maxHeight={300}
onOptionChange={handleChange}
options={selectOptions}
placeholder="Choose country"
placement="bottom-start"
required
value={value}
onScrollToBottom={hasMoreOptions ? expandOptions : undefined}
optionsLoading={optionsLoading}
/>
</FormGroup>
<FormGroup>
<ComboBox
filterable={true}
label="Countries (Search after 2 letters)"
maxHeight={300}
onOptionChange={handleSearchOptionChange}
options={searchOptions}
placeholder="Type at least 2 letters"
placement="bottom-start"
required
value={searchValue}
optionsLoading={searchLoading}
onInputChange={handleSearchInputChange}
/>
</FormGroup>
</Form>
</Box>
);
};
export default ComboBoxTest;