g2 icon indicating copy to clipboard operation
g2 copied to clipboard

FlatList: Add component

Open ItsJonQ opened this issue 5 years ago • 5 comments

I feel that it's important that G2 provides some sort of "FlatList" component (even if it's a basic one).

(Drawing inspiration from the world of native development...)

What's a FlatList?

FlatLists are a feature-rich and high-performant way to render collections of things.

Features

Below are some of the very helpful features that a FlatList component should offer out-of-the-box (speaking purely from a web-based context)

  • Programatic scroll to... (item, index, top, bottom, etc...)
  • Scroll based callbacks (e.g. when scrolling reaches bottom)
  • Built-in infinite loading mechanics
  • Built-in refresh (or fetch) mechanics
  • Simple sectioning of headers, separators, footers
  • Sticky headers
  • Can be enhanced to support "virtualization" or "windowing" for loading MANYYYY items with high perf (check out React Window as an example)
  • Can be enhanced with drag/sort

What's it look like?

From a visual UI perspective, FlatLists can resemble many forms. The most familiar example, would be something like this:

1_tpL2xhs1TGEQ09AwKqkYYQ

The above example is typical of a "Contact List" in contact/messaging-based applications.

Stepping away from mobile....

In Gutenberg, the Block Inserter sidebar can be imagined as a FlatList:

Screen Shot 2020-08-06 at 9 21 22 AM

From the G2 storybook, the following would use a FlatList as well:

Screen Shot 2020-08-06 at 9 33 03 AM

https://g2-components.xyz/?path=/story/examples-blocks--layout-grid

It's usefulness extends beyond just the Editor UI. 3rd party block authors, plugin authors, and others can use this component as a way to add "native" (editor) support to their data collections.

What's the code going to look like?

I don't think it is necessary to replicate the exact API interfaces from React Native's FlatList. To start, I think it should have some basic props, like...

<FlatList
    data={[...]}
    renderItem={}
    listHeader={}
    numColumns={}
/>

Also a way to enable programatic sortTo... methods.

We can try to recreate the BlockInserter as a starting test case!

ItsJonQ avatar Aug 06 '20 13:08 ItsJonQ

Ohhh boy! Some fun updates. I just prototyped out the initial feature set + component API for <FlatList />. It's inspired very heavily on React Native's implementation as well as List from Swift UI.

Demo:

Live Demo: https://g2-components.xyz/?path=/story/components-flatlist--default

More importantly... check out the code!!!!

const Example = () => {
  const [users, setUsers] = useFlatListState(userSchema.make(10));

  const addUser = () => setUsers.prepend(userSchema.makeOne());
  const deleteUser = (id) => setUsers.delete({ id });
  const moveUser = (from, to) => setUsers.move(from, to);

  return (
    <HStack>
      <FlatList
        css={{ maxWidth: 400 }}
        onMove={(from, to) => moveUser(from, to)}
      >
        <HStack alignment="edge">
          <Button onClick={addUser} variant="primary">
            Add User
          </Button>
          <EditButton />
        </HStack>
        <FlatListItems>
          {users.map((user, index) => {
            return (
              <FlatListItem
                css={{ padding: 12 }}
                key={user.id || index}
                onDelete={() => deleteUser(user.id)}
              >
                <HStack alignment="left" spacing={3}>
                  <Avatar src={user.avatar} />
                  <Spacer>
                    <VStack spacing={1}>
                      <Text size={14} weight="bold">
                        {user.name}
                      </Text>
                      <Text
                        numberOfLines={2}
                        size={12}
                        truncate
                        variant="muted"
                      >
                        {user.description}
                      </Text>
                    </VStack>
                  </Spacer>
                </HStack>
              </FlatListItem>
            );
          })}
        </FlatListItems>
      </FlatList>
    </HStack>
  );
};

Key features

  • FlatList can detect if editing is enabled via a prop or the presence of an <EditButton />
  • Adding an onMove callback enables sorting
  • Adding an onDelete callback enables deleting
  • useFlatListState is an enhanced useState, with convenient + common array methods like add, delete, move, etc...

ItsJonQ avatar Aug 13 '20 17:08 ItsJonQ

Nicely gelatinous animation, love it!

We use a mover control (up/down arrow) in many places, not just blocks but in the block navigator as well. This is based on the idea that drag and drop is additive to explicit button actions. Do you have any thoughts on how to best handle that?

jasmussen avatar Aug 14 '20 09:08 jasmussen

@jasmussen Haii!! I'm not sure yet! We could offer arrows along side the drag handle.

(Feel free to share design ideas if you have any!)

Or maybe.. we could offer a "mode" where the user can prefer drag handles, mover arrows, or both?

This could be a preference like dark mode, etc...

ItsJonQ avatar Aug 15 '20 20:08 ItsJonQ

We could offer arrows along side the drag handle.

It does become a lot, but it also seems necessary. I'd probably just use the two-line drag handle that the block editor uses.

Or maybe.. we could offer a "mode" where the user can prefer drag handles, mover arrows, or both?

If we were to offer a mode, I'd think it was a toggle of the mover control, not drag & drop vs. arrows. For example exactly as shown in this gif, you enter and exit a rearranging "mode".

User options remain a plan B, but even then we'd want to choose a default option, and it would have to be the one that balances the accessibility aspect best.

jasmussen avatar Aug 17 '20 08:08 jasmussen

More ideas on list types + interactions: https://material.io/components/lists#types

ItsJonQ avatar Oct 05 '20 21:10 ItsJonQ