Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(multicolumnautocomplete): first implementation #670

Merged
merged 17 commits into from
Nov 22, 2023

Conversation

masoudmanson
Copy link
Contributor

@masoudmanson masoudmanson commented Oct 31, 2023

Summary

Autocomplete, DropdownMenu, and Dropdown
Github issue: #559

Enhancing the Autocomplete, DropdownMenu, and Dropdown components to support multiple columns. This update enables users to create multi-column dropdowns by introducing a new type of options array to the autocomplete component.

Single-Column

The Autocomplete component will continue rendering a single-column autocomplete by passing a list of options as before. No alterations have been made to the previous usages of the Autocomplete or Dropdown components.

const AUTOCOMPLETE_SINGLE_COLUMN_OPTIONS = [
  {
    name: "Status: can't reproduce",
    section: "name only",
  },
  {
    name: "Status: confirmed",
    section: "name only",
  },
  {
    count: 3,
    name: "Status: duplicate",
    section: "name with count",
  },
  {
    count: 5,
    name: "Status: needs information",
    section: "name with count",
  },
  {
    details: "This will not be worked on",
    name: "Status: wont do/fix",
    section: "name with details",
  },
];
Screenshot 2023-11-20 at 5 42 04 PM

Multi-Column

The provided structure for the new options will enable the rendering of a multi-column Autocomplete/Dropdown component. Each column within this structure includes an options array identical to the single-column Autocomplete's options array.

The AutocompleteMultiColumnOption type is defined as below:

export type AutocompleteMultiColumnOption<
  T,
  Multiple extends boolean | undefined,
  DisableClearable extends boolean | undefined,
  FreeSolo extends boolean | undefined
> = {
  icon?: React.ReactNode;
  name: string;
  options: T[];
  props?: Partial<
    AutocompleteBaseProps<T, Multiple, DisableClearable, FreeSolo>
  >;
  width?: number;
};

See the example below:

const AUTOCOMPLETE_MULTI_COLUMN_OPTIONS = [
  {
    icon: <Icon sdsIcon="chevronRight" sdsSize="xs" sdsType="static" />,
    name: "Column One",
    options: [
    // ...items
    ],
    width: 260,
  },
  {
    icon: <Icon sdsIcon="chevronRight" sdsSize="xs" sdsType="static" />,
    name: "Column Two",
    options: [
    // ... items
    ],
    width: 260,
  },
  {
    name: "Column Three",
    options: [
    // ...items
    ],
    width: 180,
  },
];
Screenshot 2023-11-20 at 5 51 55 PM

Implementation Details

Two new components have been integrated into SDS to introduce the Single-Column/Multi-column functionality using MUI's Autocomplete. These components are the AutocompleteBase and the AutocompleteMultiColumn. The SDS's Autocomplete component determines which one to render based on the structure of the options array.

AutocompleteBase

AutocompleteBase is a simple wrapper around MUI's Autocomplete component, enhancing it with specific styles and additional functionalities.

AutocompleteMultiColumn

AutocompleteMultiColumn is responsible for rendering Multi-column Autocompletes. It iterates through the provided options array, creating distinct MUI Autocomplete components for each column. These individual Autocomplete components are then encapsulated within a Popper component for display.

Controllable Autocomplete

To transform the SDS Autocomplete into a controlled component, supply the value and onChange props to the component. A basic implementation for a controlled Autocomplete can be exemplified as follows:

const AutocompleteDemo = <
  T extends DefaultAutocompleteOption,
  Multiple extends boolean | undefined,
  DisableClearable extends boolean | undefined,
  FreeSolo extends boolean | undefined
>(
  props
): JSX.Element => {
  const { multiple, options } = props;

  const [value, setValue] = useState<
    SDSAutocompleteValue<T, Multiple, DisableClearable, FreeSolo>
  >(
    (multiple ? [] : null) as SDSAutocompleteValue<T, Multiple, DisableClearable, FreeSolo>
  );

  return (
    <RawAutocomplete
      multiple={multiple}
      onChange={handleChange}
      options={options}
      value={value}
    />
  );

  function handleChange(
    _: SyntheticEvent<Element, Event>,
    newValue: SDSAutocompleteValue<T, Multiple, DisableClearable, FreeSolo>
  ) {
    setValue(newValue);
  }
};

Breaking Changes

1. Autocomplete option type

We've replaced DefaultDropdownMenuOption with DefaultAutocompleteOption as the preferred choice. However, we've exported this line for backward compatibility to prevent potential TypeScript type issues for product teams using previous versions.

export type DefaultDropdownMenuOption = DefaultAutocompleteOption;

2. OnChange Function Parameters

The onChange function within the Autocomplete component now allows the use of four parameters, as detailed below:

type OnChange<
  T,
  Multiple,
  DisableClearable,
  FreeSolo
> = (
  event: React.SyntheticEvent,
  value: SDSAutocompleteValue<T, Multiple, DisableClearable, FreeSolo>,
  reason: AutocompleteChangeReason,
  details?: AutocompleteChangeDetails<T>
) => void;

3. Autocomplete Value Structure

The Autocomplete/Dropdown value structure varies between Single and Multi-column Autocompletes.

Single-column Autocomplete:

For multiple = false:

value = DefaultAutocompleteOption;

For multiple = true:

value = DefaultAutocompleteOption[];

Multi-column Autocomplete:

For multiple = false:

value = {
  firstColumnName: DefaultAutocompleteOption,
  secondColumnName: DefaultAutocompleteOption,
  // ...
};

For multiple = true:

value = {
  firstColumnName: DefaultAutocompleteOption[],
  secondColumnName: DefaultAutocompleteOption[],
  // ...
};

4. Adjusting Dropdown Menu Width

Customizing the width of the Dropdown component's menu is straightforward. You can set the desired width by passing a width prop to the underlying DropdownMenu component.

<Dropdown
  DropdownMenuProps={{
    width: 300, // Set your preferred width here
  }}
  {...props}
/>

Checklist

  • Default Story in Storybook
  • LivePreview Story in Storybook
  • Test Story in Storybook
  • Tests written
  • Variables from defaultTheme.ts used wherever possible
  • If updating an existing component, depreciate flag has been used where necessary
  • Chromatic build verified by @chanzuckerberg/sds-design

@masoudmanson masoudmanson requested a review from tihuan November 1, 2023 02:42
@masoudmanson masoudmanson added Work in Progress This PR is a work in progress Ready for review This PR is ready for review labels Nov 1, 2023
@masoudmanson masoudmanson self-assigned this Nov 1, 2023
@masoudmanson masoudmanson removed the Ready for review This PR is ready for review label Nov 8, 2023
@tihuan
Copy link
Contributor

tihuan commented Nov 14, 2023

Hey Masoud 😄 ! Just curious, not sure if you got a chance to explore the possibility of keeping just one Autocomplete component that's capable of handling single and multi columns?

Thank you!

@masoudmanson
Copy link
Contributor Author

masoudmanson commented Nov 14, 2023

Hey Masoud 😄 ! Just curious, not sure if you got a chance to explore the possibility of keeping just one Autocomplete component that's capable of handling single and multi columns?

Thank you!

Hey @tihuan,

That's exactly what I've done in this PR, we now have an Autocomplete component that renders a SingleColumn or MultiColumn Autocomplete based on the options prop given to it!

if the options prop is a simple array, the result would be a SingleColumn Autocomplete:

const OPTIONS = [
{
    name: "Status: can't reproduce",
    section: "name only",
  },
  {
    name: "Status: confirmed",
    section: "name only",
  },
  {
    count: 3,
    name: "Status: duplicate",
    section: "name with count",
  }
];

Screenshot 2023-11-14 at 12 33 42 PM

And if the options is an array with the following structure, the result would be a MultiColumn dropdown:

const OPTIONS = [
  {
    icon: <Icon sdsIcon="chevronRight" sdsSize="xs" sdsType="static" />,
    name: "columnOne",
    width: 250,
    options: [
      {
        description: "Good for newcomers",
        name: "good first issue",
      },
      {
        description: "Extra attention is needed",
        name: "help wanted",
      },
    ],
  },
  {
    icon: <Icon sdsIcon="chevronRight" sdsSize="xs" sdsType="static" />,
    name: "columnTwo",
    width: 200,
    options: [
      {
        description: "needs to be fixed",
        name: "bug",
      },
      {
        description: "needs to be implemented",
        name: "feature",
      },
    ],
  },
];

Screenshot 2023-11-14 at 12 34 40 PM

To check both instances, change the options prop for the Autocomplete component in Storybook.

@masoudmanson masoudmanson added Ready for review This PR is ready for review and removed Work in Progress This PR is a work in progress labels Nov 14, 2023
@tihuan
Copy link
Contributor

tihuan commented Nov 14, 2023

Ah cool! That's amazing ⭐ Thanks for doing that!

So does that mean AutocompleteMultiColumn can replace Autocomplete eventually?

Because if that's the case, I wonder if we could just do a breaking change and replace Autocomplete with AutocompleteMultiColumn in this PR?

@masoudmanson
Copy link
Contributor Author

masoudmanson commented Nov 14, 2023

Ah cool! That's amazing ⭐ Thanks for doing that!

So does that mean AutocompleteMultiColumn can replace Autocomplete eventually?

Because if that's the case, I wonder if we could just do a breaking change and replace Autocomplete with AutocompleteMultiColumn in this PR?

Thank you, 😍

Nothing needs to be replaced because the old Autocomplete can handle both. If you pass the old options array, it will generate a SingleColumn Autocomplete, but if you pass the new structure, it will generate a MultiColumn Autocomplete!

So, the product teams should not notice any difference if they continue to pass the old options array!

Actually, I think we should skip exporting the AutocompleteBase and AutocompleteMultiColumn and just export the Autocomplete!

@tihuan
Copy link
Contributor

tihuan commented Nov 14, 2023

Ooh that's perfect! Sorry I think I missed the important fact that Autocomplete uses AutocompleteMultiColumns under the hood and was thinking that AutocompleteMultiColumns was a replacement of Autocomplete lol

Removing AutocompleteMultiColumns and AutocompleteBase from export sounds great to me, since they are implementation details 🙆‍♂️

Thanks so much for the amazing work, Masoud!

@tihuan
Copy link
Contributor

tihuan commented Nov 14, 2023

A followup question, since we're removing exports of AutocompleteBase and AutocompleteMultiColumn, I suppose we can restructure stories accordingly to be all under Autocomplete component?

I'll do the review after the changes above are done, if that's okay!

Thanks again!

@masoudmanson
Copy link
Contributor Author

A followup question, since we're removing exports of AutocompleteBase and AutocompleteMultiColumn, I suppose we can restructure stories accordingly to be all under Autocomplete component?

I'll do the review after the changes above are done, if that's okay!

Thanks again!

A followup question, since we're removing exports of AutocompleteBase and AutocompleteMultiColumn, I suppose we can restructure stories accordingly to be all under Autocomplete component?

I'll do the review after the changes above are done, if that's okay!

Thanks again!

Thank you for your feedback and guidance @tihuan
I'll let you know once the changes are pushed. 😬

@tihuan
Copy link
Contributor

tihuan commented Nov 14, 2023

Always a pleasure, Masoud! Really appreciate your fantastic work as always 🎆 🥇 !!

…Column exports and stories

and add both under the broader Autocomplete component
…ultiColumn into

the Autocomplete/components directory
Copy link

@clarsen-czi clarsen-czi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good @masoudmanson ! A few changes still to make

@masoudmanson masoudmanson requested a review from tihuan November 17, 2023 16:51
Copy link
Contributor

@hthomas-czi hthomas-czi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, @masoudmanson!

The only change I'd request is adding a Boolean property for the right chevron between the columns. That implies a relationship between the columns which isn't always wanted.

@masoudmanson
Copy link
Contributor Author

masoudmanson commented Nov 20, 2023

Looks great, @masoudmanson!

The only change I'd request is adding a Boolean property for the right chevron between the columns. That implies a relationship between the columns which isn't always wanted.

The options array allows you to change the icons, names, and widths for each column. I've added the option to define an icon for each column so users can remove it by not adding a 'SdsIcon' prop in the column data.

  1. Three-column Autocomplete without relation icons:
OPTIONS = [
  {
    name: "Column 1",
    width: 260,
    options: [
      ...
    ],
  },
  {
    name: "Column 2",
    width: 260,
    options: [
     ...
    ],
  },
  {
    name: "Column 3",
    width: 170,
    options: [
      ...
    ],
  },
];

Screenshot 2023-11-20 at 2 29 49 PM

  1. Three-column Autocomplete with custom relation icons:
OPTIONS = [
  {
    icon: <Icon sdsIcon="chevronRight" sdsSize="xs" sdsType="static" />,
    name: "Column 1",
    width: 260,
    options: [
      ...
    ],
  },
  {
    icon: <Icon sdsIcon="chevronRight2" sdsSize="xs" sdsType="static" />,
    name: "Column 2",
    width: 260,
    options: [
     ...
    ],
  },
  {
    name: "Column 3",
    width: 170,
    options: [
      ...
    ],
  },
];

Screenshot 2023-11-20 at 2 31 42 PM

@masoudmanson
Copy link
Contributor Author

Looking good @masoudmanson ! A few changes still to make

@clarsen-czi.
Thank you for reviewing the PR. I've made the changes you asked for. I'd appreciate it if you could look at the result again.

@masoudmanson masoudmanson dismissed clarsen-czi’s stale review November 20, 2023 19:41

Changes have been made.

Copy link

@clarsen-czi clarsen-czi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more changes, but almost there. Thanks @masoudmanson !!

Copy link
Contributor

@tihuan tihuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Monumental work here, @masoudmanson 😮 🥇 🏆 👏 👏 👏

Really a true testament of your mad skillz and perseverance to get through this!

Thanks so much again for this impressive work 💎 !!

Just some small nits here otherwise LGTM 🚀 !

packages/components/src/core/AutocompleteBase/index.tsx Outdated Show resolved Hide resolved
packages/components/src/core/AutocompleteBase/index.tsx Outdated Show resolved Hide resolved
packages/components/src/common/utils.ts Show resolved Hide resolved
packages/components/src/core/DropdownMenu/index.tsx Outdated Show resolved Hide resolved
packages/components/src/core/DropdownMenu/style.ts Outdated Show resolved Hide resolved
packages/components/src/core/DropdownMenu/style.ts Outdated Show resolved Hide resolved
@@ -136,7 +136,7 @@ const InputSearch = forwardRef<HTMLDivElement, InputSearchProps>(
* (masoudmanson): This prevents the browser's default auto completion
* menu from being displayed for the InputSearch.
*/
autoComplete="off"
autoComplete="one-time-code"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird, but autoComplete="off" seems ineffective in some cases now. Browsers are introducing new potential values for the autocomplete attribute!

https://stackoverflow.com/questions/12374442/chrome-ignores-autocomplete-off

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahaha the saga continues!

I remember reading the same thread and used their 2021 recommended solution of combining type="search" and autoComplete"off" here

But I guess that doesn't work anymore 😮‍💨

@masoudmanson
Copy link
Contributor Author

Huge thanks for diving into the PR with such attention to detail, @tihuan! 😍
Your thorough review means a lot. I've done my best to squash all the bugs and tidy up the code.

You're awesome! 🥳🙏🏻

@masoudmanson masoudmanson dismissed clarsen-czi’s stale review November 22, 2023 07:03

Styles have been updated per our discussion!

BREAKING CHANGE: Autocomplte options type has changed from DefaultDropdownMenuOption to
DefaultAutocompleteOption.
Copy link
Contributor

@tihuan tihuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My pleasure to review your fantastic PR as always, @masoudmanson 😄 🙏 !!

Just some tiny comments left!

And one last thing is perhaps adding a breaking change commit, so we get the proper version bump and change log 👌

Thanks again for all the amazing work on this!

Copy link
Contributor

@tihuan tihuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM to the max!! 💯 🔥

Thanks again for everything, Masoud!!

@masoudmanson
Copy link
Contributor Author

LGTM to the max!! 💯 🔥

Thanks again for everything, Masoud!!

Woot woot! 🎉🔥
Big thanks for the final review, @tihuan!
Really appreciate your guidance and support throughout. 😍

@masoudmanson masoudmanson merged commit 77342c6 into main Nov 22, 2023
7 checks passed
@masoudmanson masoudmanson deleted the 559-multi-column-dropdown branch November 22, 2023 18:23
@tihuan
Copy link
Contributor

tihuan commented Nov 22, 2023

LGTM to the max!! 💯 🔥
Thanks again for everything, Masoud!!

Woot woot! 🎉🔥 Big thanks for the final review, @tihuan! Really appreciate your guidance and support throughout. 😍

You did all the important work, my man! Thanks again 🤩 🥇 !!

@masoudmanson masoudmanson added Released and removed Ready for review This PR is ready for review labels Oct 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement: Multi-column Dropdown Menu Multicolumn dropdown - Build
4 participants