ItsJonQ / g2

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

Navigation: Add component #20

Open ItsJonQ opened 3 years ago

ItsJonQ commented 3 years ago

I got a VERY early and rough prototype of a Navigation component during early development of G2: https://g2-components.xyz/iframe.html?id=components-navigator--default&viewMode=story#

Having a feature-rich<Navigation /> component would be a VERY welcomed addition in the world of web components. Some features would include:

(The React Native world already has this <3)

I initially wrote about the requirements, mechanics, and breakdown in detail here: https://github.com/WordPress/gutenberg/pull/24107#issuecomment-662578255

Visual Prototype

An example of this experience can be seen in this CodePen (by @jameskoster) https://cdpn.io/jameskoster/debug/vYGEEpp#

Link to Figma file: https://www.figma.com/file/e4tLacmlPuZV47l7901FEs/WordPress-Design-Library?node-id=1456%3A72

Another Figma Prototype: https://www.figma.com/proto/QNNc834dBBNz1bL2qc9VZyq5/Navigation-experimental?node-id=1765%3A4323&viewport=517%2C73%2C0.14826907217502594&scaling=scale-down

ItsJonQ commented 3 years ago

FYI

I worked on a similar system during the creation of Help Scout's Beacon: https://www.helpscout.com/live-chat/

The UI/experience is different. However, the underlying Nav-based system will almost be identical.

Some points from that experience...

ItsJonQ commented 3 years ago

Updates! I have something working:

Demo: https://g2-components.xyz/iframe.html?id=components-navigator--sidebar

Nested screens work, along with "active" awareness, back/forward navigation awareness + animation sync.

The Navigation component code does work, however, I would not be comfortable using it outside of prototyping. The primary thing that needs to be fixed would be the (current) reliance on React Router Context. The current work-around to avoid app integration conflict is to use a namespace param (which isn't ideal).

Anywhoo!! It looks + feels promising :)

Check out the code needed to create this experience: https://github.com/ItsJonQ/g2/blob/master/packages/components/src/Navigator/__stories__/Navigator.Sidebar.stories.js

The components + markup is simple, and should feel very familiar to those who have used React Router (or any routing library). There's room for improvement, but we should continue with this DX in mind!

Things to Improve

NavigationLink composition

One thing I'd like to improve is this:

<NavigatorLink to="Home">
    <MenuItem>My Home</MenuItem>
</NavigatorLink>

We should have to nest a component under <NavigatorLink />. Instead, it should be this:

<NavigatorLink as={MenuItem} to="Home">
    My Home
</NavigatorLink>

However, this appears to be a limitation with React Router (for the time being)

Animations

Right now, it's being handled by react-transition-group. I'd like to refactor this in favour of components from the Animation system (@wp-g2/animations). It uses Framer Motion under the hood, which is VERY capable in seamlessly handling stateful mount/unmount transitions.

This will also give us core-level reducedMotion support for free āœŒļø

MenuItem

The example navigation uses another component for it's item UI, MenuItem. MenuItem is based on List from Material UI:

https://material-ui.com/components/lists/

As such, it should be enhanced to enable (left aligned) Icon rendering, as well as other features that List offers - such as right aligned actions, a badge count, etc...

ItsJonQ commented 3 years ago

Haiii! An interesting update.. I started learning me some Swift UI.

Today, I learned about their NavigationView.

Oh, my, goodness. I'm blown away with how easy Navigation is within Swift UI.

Below is a simple example:

NavigationView {
    NavigationLink(destination: ProductsView()) {
        Text("Products")
    }

    NavigationLink(destination: SettingsView()) {
        Text("Settings")
    }
    ...
}

Animations, gestures, title transitions, back/forward history, all of that good stuff... It's all included with NavigationView.

I hope we're able to provide a similar experience with these Navigation components šŸ™

ItsJonQ commented 3 years ago

Updates!!

I've created a more complex example based on this Codepen by @jameskoster: https://cdpn.io/jameskoster/debug/vYGEEpp

Video demo: https://d.pr/v/jDu8uU

Story link: https://g2-components.xyz/iframe.html?id=components-navigator--sidebar

It showcases:

Taking a look at the code: https://github.com/ItsJonQ/g2/blob/master/packages/components/src/Navigator/__stories__/Navigator.Sidebar.stories.js

There isn't all that much happening! Most of it is UI composition. The <Navigator /> component handles internal state management, animation sequencing, etc... as it should be āœŒļø šŸ˜Š

Note: The "Back" nav link for the WooCommerce plugin is wonky as the collection of WooCommerce plugin links are dummy ones, which cause the history to be wonky.

psealock commented 3 years ago

Thanks for this @ItsJonQ ! Very nice to see what ideas you have about implementation. The work @joshuatf and I are doing currently is focused on the component's ability to accept data to programatically create the equivalent of your PostsNav or PagesNav components.

ItsJonQ commented 3 years ago

@psealock Thanks! Hopefully these examples can help!

ItsJonQ commented 3 years ago

Another update! I've updated the Story with a mock browser bar to help better illustrate the integrated route state changes:

Screen Shot 2020-08-13 at 11 07 48 AM

https://g2-components.xyz/iframe.html?id=components-navigator--sidebar

ockham commented 3 years ago

I'd like to offer a different perspective on the router problem that you first discussed in https://github.com/WordPress/gutenberg/pull/24107#issuecomment-662578255:

Router?

The above behaviours are typically includes as part of a Router system. (e.g. React Router). Where we run into some issues is...

  1. Gutenberg doesn't use a Router (at all).
  2. Using a Router (e.g. React Router) for the a nav menu means that it will not be compatible with 3rd party apps using the same Router. (Router context cannot be gracefully shared, as the libraries are built to assume 1 single router instance).

In other words... We'd like to have the feature-set of a Router, without directly using a library (like react-router).

This sounds quite similar to the rationale that lead to the development of @wordpress/element: We wanted React's API, but didn't want to commit to it as our library, in case we ever wanted to replace it by something else.

I'd argue that if the requirements for a library are so well-defined and narrow as in the case of this router (map a route to a component hierarchy), and there exists one industry standard for it, we might as well embrace it. I think it's a bit of wishful thinking that we can become fully library-independent if we only hide it behind an abstraction layer that's effectively replicating its interface (and I can list a number of issues that we've encountered with @wordpress/element being identical-to-React-but-not-quite).

I'd suggest we use React Router (since Reach is going to be 'merged' into it). We had some good experience with it while working on the Gutenboarding project in Calypso. I think it's even a thin enough abstraction layer that I think it can even be interoperable with other routing libraries (that serve other routes on the same server). I'm happy to elaborate more if needed :smile:

cc/ @sirreal who co-championed React Router in Gutenboarding

sirreal commented 3 years ago

I'd leave a strong vote for using a library and against implementing something when an OSS library (with a compatible license) already has the features we want.

The react / @wordpress/element situation is very painful at times, but I don't think this is likely to be so relevant. Perhaps https://github.com/WordPress/gutenberg/pull/24107 is the most appropriate place to discuss whether or not to just use react-router?

It seems to me that the only argument against using react-router was the fact that it's context don't nest well:

Using a Router (e.g. React Router) for the a nav menu means that it will not be compatible with 3rd party apps using the same Router. (Router context cannot be gracefully shared, as the libraries are built to assume 1 single router instance).

Can we explore that space? That seems like a surmountable problem if its the only argument against react-router.

ItsJonQ commented 3 years ago

@ockham + @sirreal Haii! Thank you for your feedback!

For context, the current implementation does use React Router āœŒļø .

Previous to this, I had explored other Router solutions (Wouter, Reach, and others). However, React Router's feature set/flexibility made the most sense.

Unfortunately, React Router's code was "forked" (copied/pasted) into G2. I strongly prefer NOT to have forked it in this manner, but I had to.

We can certainly explore it. I've tried šŸ˜… . Others are free to give it a shot.

React Router's components (e.g. Route, Link, etc...) are bound to the internal context instance of Router and History. On the plus side, the Router components are fairly minimal, which is why it didn't feel awful to copy/paste them into the G2 Project (still bad, but not awful).

For our purposes, I wish they were more flexible. Similar to how Reakit's components are context/state agnostic, until you pass things in: https://reakit.io/docs/radio/

However, I do understand that this use-case is probably not common. As React Router was designed for apps (not necessarily libraries), and apps almost always use a single routing system.

ItsJonQ commented 3 years ago

Additional ideas here:

https://github.com/ItsJonQ/g2/issues/198