Skip to content

Conversation

@davelinke
Copy link
Contributor

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

combobox

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;

@changeset-bot
Copy link

changeset-bot bot commented Jun 13, 2025

⚠️ No Changeset found

Latest commit: f02745a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@davelinke
Copy link
Contributor Author

Hello @chanceaclark. We have talked about this before.

There's the need of a combo box component that can load pages of options as we scroll down as well as many other functionalities like searching.

Can you suggest what other functionalities would you like to see in such a component before i submit a formal PR?

@davelinke
Copy link
Contributor Author

@leeBigCommerce , would you also give me your input?

@davelinke davelinke force-pushed the component/combo-box branch from 1853e3f to f02745a Compare July 9, 2025 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants