ItsJonQ / g2

✨ An experimental reimagining of WordPress components
http://g2-components.com/
MIT License
105 stars 12 forks source link

Systems: The Core of a Component Library #3

Closed ItsJonQ closed 4 years ago

ItsJonQ commented 4 years ago

I've been thinking deeply about design systems, specifically component libraries, for a while now. An indicator of a successful library is something that...

Just Works.

Meaning, the person using the library can plop things in and hit the ground running, with minimal configuration, prop "massaging", and setup.

The components are self-aware. They're aware of each other. Accessibility, styles, animations, and gestures are built-in by default.

It. Just. Works.

How a library can achieve this workflow is for it to recognize and craft high-quality core systems.

Core Systems Overview

component-system-parts

Above is a rough diagram that illustrates a high-level overview of the various core systems.

These systems would exist at the lowest level of the library. They can be exposed to consumers, however, their interfaces (APIs) should be greatly simplified.


Note: There are even smaller sub-systems in these core systems. For example, Reakit (❤️), uses Popper.js for Popover positioning. Popper.js is a dedicated positioning engine (system).


From a development perspective, I would break it down into 3 levels (lowest to highest)

  1. Core
  2. Library
  3. Consumer

Core

These systems can exist within the project or as 3rd party dependencies, like Reakit (a11y) or Framer Motion (Animation/Gesture). The APIs for working with these parts should be thoughtful and well defined.

If we're thinking about this from an Atomic Design perspective (in the context of a component library)... These would be electrons, neutrons, and photons (not even atoms!!!!)

Library

This is where our components live. I would split these into 2 categories:

  1. Primitives
  2. Non-Primitives (I don't know what else to call it)

Primitives

Primitives are the base-levels components. In terms of Atomic Design, these are the atoms. The best example of this would be the BaseView or Box component.

Box interfaces directly with the core systems. Beyond that, everything else uses Box.

Other examples of primitives may include:

Core system features can be exposed with simplified APIs (aka. component props)

Non-Primitives

Non-Primitives (lol, I need a better name), are elements people most often think about when it comes to Component libraries. These are your Card, Button, TextInput, etc... In terms of Atomic Design, these are the molecules.

They are mostly composed of primitives and work relatively closely with the core systems.

They can be composed together to create larger components (or "molecules").

For example, a ColorPicker would be composed of many elements, like:

It may also interface with some of the core systems. (e.g. gestures for dragging).

Consumer

This level exists outside of the library. This is where developers grab our components and build their UIs.

They should be able to compose components together with ease. If needed, they should be able to easily make refinements to styles (style system/theme system) or maybe animation feels (animation system). The APIs for making these adjustments should be incredibly minimal.

Below would be an example of minimal consumer friendly APIs.

Let's say we want to make something draggable.

Here is react-spring / react-use-gesture:

function PullRelease() {
  const [{ x, y }, set] = useSpring(() => ({ x: 0, y: 0 }))

  // Set the drag hook and define component movement based on gesture data
  const bind = useDrag(({ down, movement: [mx, my] }) => {
    set({ x: down ? mx : 0, y: down ? my : 0 })
  })

  // Bind it to a component
  return <animated.div {...bind()} style={{ x, y }} />
}

Here is framer-motion

const constraintsRef = useRef(null)

return (
  <motion.div ref={constraintsRef}>
    <motion.div
      drag
      dragConstraints={constraintsRef}
    />
  </motion.div>
)

Framer Motion feels much more approachable. It's easier to read, also, it feels easier to write and modify.

It does a ton of things under-the-hood, but all of that should be taken care of at the core/library level.

It should feel invisible to the consumer.


I understand this may feel a little abstract. To get a (slightly) better sense of things, it may help to explore this repo a bit!

That's it for now :). I'll add to these notes as I continue my exploration and research ✌️

ItsJonQ commented 4 years ago

cc'ing @diegohaz <3

diegohaz commented 4 years ago

Thank you for sharing this! ❤️

I agree with almost everything. I only have two observations:

Framer Motion feels much more approachable. It's easier to read, also, it feels easier to write and modify.

I agree that Framer Motion looks better, but if I had to choose between Framer Motion and React Spring to use in a component library, I would probably go for the one that has the most flexible API and gives me more control. So, in this case, I would use React Spring internally and maybe expose an API similar to Framer Motion to my consumers.

If we ever need to build something different, Framer Motion may not have a prop that supports it. Whereas with a lower level API like the one React Spring exposes, we have more power over what we can do. But I don't know Framer Motion enough to discard it. Maybe it exposes some lower level APIs just like Emotion.

These are your Card, Button, TextInput, etc... In terms of Atomic Design, these are the molecules.

Not really important, but I think these components would actually be classified as atoms. Some of them are even mentioned as atoms in the Brad Frost's Atomic Design blog post.

But the fact that this is confusing and we have to discuss it is just a signal that Atomic Design has failed in structuring design systems, at least when it comes to code. I've received a lot of feedback while working on Atomic React, and I could see that the time people were wasting trying to decide whether a component is an atom, molecule or organism wasn't worth the benefit that the methodology was providing. I tried to remedy it (Do not worry), but it turns out that Atomic Design is another problem. 😅

ItsJonQ commented 4 years ago

I agree that Framer Motion looks better, but if I had to choose between Framer Motion and React Spring to use in a component library, I would probably go for the one that has the most flexible API and gives me more control. So, in this case, I would use React Spring internally and maybe expose an API similar to Framer Motion to my consumers.

Ah yes! I agree with you there. Ultimately, at the core/library level, I'd want maximum control. But for consumers, simple API.

That may be something like...

Framer Motion throughout. or... React Spring + Gestures for the core/base. But, wrapped/simplified by Motion-like Component API layer for consumers.

Not really important, but I think these components would actually be classified as atoms.

I recognize that those are typically considered atoms 😊 . I think what I was attempting to illustrate is that components that are often considered to be lowest level elements (e.g. Button or Card), are in practice, often not.

They're often composed of many smaller components.

For context, I'm not 100% subscribed to Atomic Design philosophy. I like it as a guideline and a way to imagine things.

The discoveries from your Arc project are invaluable! I think there's a lot of nuance in this work. Nuance with multiple correct answers for different projects/setups.

Like you had mentioned, I think getting caught up in semantics for semantics sake can cause problems 🙈 .


I suppose the TLDR is...

Low level/core systems are hugely important. Create intelligent + powerful primitives that are to be used everywhere (e.g. Box). Compose as much as possible.

✌️

ItsJonQ commented 4 years ago

Oo! This is fun.

Below is an illustration of a flow chart, detailing the various systems and mechanics for an <Alert /> component:

IMG_0457

Example: https://g2-components.xyz/?path=/story/components-alert--default

ItsJonQ commented 4 years ago

Closing this up as we now have a blog to express and share ideas: https://g2components.wordpress.com/